2011-01-27 3 views
4

Я пытаюсь создать второй цикл сообщения для асинхронного обработки/фильтрации сообщений низкого уровня на C#. Он работает, создавая скрытую форму, выставляя свойство Handle зацепиться и запуская второй цикл сообщений в отдельном потоке. На данный момент я доволен результатами, но я не могу нормально выйти из второго цикла. Единственным обходным решением было установить свойство IsBackground равным true, поэтому второй поток будет просто завершен (без обработки всех ожидающих сообщений) при выходе из основного приложения.Как программно выйти из второго цикла сообщений?

Вопрос в том, как правильно завершить цикл сообщений, чтобы второе приложение Application.Run() возвращалось? Я попробовал разные подходы к созданию отдельного ApplicationContext и управлению различными событиями (Application.ApplicationExit, Application.ThreadExit, ApplicationContext.ThreadExit), но все они потерпели неудачу с условиями гонки, которые я не могу отлаживать.

Подсказка? Благодаря

Это код:

public class MessagePump 
{ 
    public delegate void HandleHelper(IntPtr handle); 

    public MessagePump(HandleHelper handleHelper, Filter filter) 
    { 
     Thread thread = new Thread(delegate() 
     { 
      ApplicationContext applicationContext = new ApplicationContext(); 
      Form form = new Form(); 
      handleHelper(form.Handle); 
      Application.AddMessageFilter(new MessageFilter(filter)); 
      Application.Run(applicationContext); 
     }); 
     thread.SetApartmentState(ApartmentState.STA); 
     thread.IsBackground = true; // <-- The workaround 
     thread.Start(); 
    } 
} 

public delegate bool Filter(ref Message m); 

internal class MessageFilter : IMessageFilter 
{ 
    private Filter _Filter; 

    public MessageFilter(Filter filter) 
    { 
     _Filter = filter; 
    } 

    #region IMessageFilter Members 

    public bool PreFilterMessage(ref Message m) 
    { 
     return _Filter(ref m); 
    } 

    #endregion // IMessageFilter Members 
} 

Я использую его в основной форме конструктора следующим образом:

_Completion = new ManualResetEvent(false); 

MessagePump pump = new MessagePump(
delegate(IntPtr handle) 
{ 
    // Sample code, I did this form twain drivers low level wrapping 
    _Scanner = new TwainSM(handle); 
    _Scanner.LoadDs("EPSON Perfection V30/V300"); 
}, 
delegate(ref Message m) 
{ 
    // Asyncrhronous processing of the messages 
    // When the correct message is found --> 
    _Completion.Set(); 
} 

EDIT: Полное решение в моем ответе.

ответ

3

Вы должны передать экземпляр Form к ApplicationContext CTOR в качестве параметра:

applicationContext = new ApplicationContext(form); 

Прямо сейчас, вы, в основном создает экземпляр не-контекст, который не заботится о вашей форме закрытия.

Кроме того, это хорошая практика, чтобы сделать некоторые очистки, как удаление фильтра, когда вам не нужно больше:

Form form = new Form(); 
ApplicationContext applicationContext = new ApplicationContext(form); 
handleHelper(form.Handle); 

MessageFilter filter = new MessageFilter(filter); 
Application.AddMessageFilter(filter); 
Application.Run(applicationContext); 
Application.RemoveMessageFilter(filter); 

[Редактировать]

Если вы не хотите показать форму, тогда вы можете использовать paramaterless ctor, но вам придется закрыть контекст вручную, вызвав метод ApplicationContext.ExitThread. Этот метод фактически вызывается, когда ваша форма запускает событие FormClosed, если вы передаете форму в конструкторе.

Поскольку скрытая форма не связана с контекстом, вам необходимо выйти из них в течение некоторого времени.

+0

Нет, потому что это покажет вторую форму.Обратите внимание, что вторая форма просто поддельна и должна оставаться скрытой. Инициализация - это способ создания дескриптора окна, который будет получать сообщения. Меня не интересует итерация пользователя в этой форме. Как только основная форма закрыта, я ожидаю, что второй контекст приложения также будет закрыт. Может быть, цикл не уходит, потому что форма не закрыта ... Теперь я попробую. – ceztko

+0

Хорошо, я понял, что то, что я делаю в HandleHelper, регистрирует «что-то» в цикле сообщений, поэтому попытка запуска приложения ApplicationContext.ExitThread неопределенно. Постарайтесь понять, может ли эта «сонять» быть незарегистрированной. – ceztko

+1

Да, вам нужно будет проверить документы для этого класса «TwainSM», который вы используете. Если он реализует 'IDisposable', вы можете попытаться уничтожить его, как только процесс будет завершен (но это всего лишь идея). – Groo

2

В итоге я понял, что thread.IsBackground = true; не был плохим, потому что это был единственный способ определить «эй, я последний поток, я должен уйти». Правильная очистка ресурсов по-прежнему необходима, жесткая. Для этого необходим третий делегат для очистки ресурсов, и я просто зарегистрировал его в событии AppDomain.CurrentDomain.ProcessExit. Я даже предоставил метод ExitLoop() классу MessageLoop (был вопрос MessagePump в вопросе). Таким образом, я могу закончить цикл сообщения в любое время. Критические разделы обработчиков ExitLoop() и ProcessExit мьютексируются.

Код:

using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Windows.Forms; 
using System.Threading; 

namespace System 
{ 
    public class MessageLoop 
    { 
     #region Fields 

     private Object _Lock; 
     private ApplicationContext _ApplicationContext; 
     private CustomMessageFilter _MessageFilter; 
     private HandleProvider _ResourceCleaner; 
     private ManualResetEvent _Completion; 
     private bool _Disposed; 

     #endregion // Fields 

     #region Constructors 

     /// <summary> 
     /// Run a second message pump that will filter messages asyncronously 
     /// </summary> 
     /// <param name="provideHandle">A delegate that provide a window handle for 
     ///  resource initializing</param> 
     /// <param name="messageFilter">A delegate for message filtering</param> 
     /// <param name="cleanResources">A delegate for proper resource cleaning 
     ///  before quitting the loop</param> 
     /// <param name="background">State if the loop should be run on a background 
     ///  thread or not. If background = false, please be aware of the 
     ///  possible race conditions on application shut-down.</param> 
     public MessageLoop(HandleProvider initializeResources, MessageFilter messageFilter, 
      HandleProvider cleanResources, bool background) 
     { 
      _Lock = new Object(); 
      _ResourceCleaner = cleanResources; 
      _Completion = new ManualResetEvent(false); 
      _Disposed = false; 

      Thread thread = new Thread(delegate() 
      { 
       _ApplicationContext = new ApplicationContext(); 
       WindowHandle window = new WindowHandle(); 
       initializeResources(window.Handle); 
       _MessageFilter = new CustomMessageFilter(messageFilter); 
       Application.AddMessageFilter(_MessageFilter); 

       // Signal resources initalizated 
       _Completion.Set(); 

       // If background = true, do resource cleaning on ProcessExit event 
       if (background) 
       { 
        AppDomain.CurrentDomain.ProcessExit += 
         new EventHandler(CurrentDomain_ProcessExit); 
       } 

       // Run the message loop 
       Application.Run(_ApplicationContext); 

       // Clean resource before leaving the thread 
       cleanResources(window.Handle); 

       // Signal resources cleaned 
       _Completion.Set(); 
      }); 
      thread.SetApartmentState(ApartmentState.STA); 
      thread.IsBackground = background; 
      thread.Start(); 

      // Before returning the instace, wait for thread resources initialization 
      _Completion.WaitOne(); 
     } 

     #endregion // Constructors 

     #region Inquiry 

     /// <summary> 
     /// Early exit the message loop 
     /// </summary> 
     public void ExitLoop() 
     { 
      lock (_Lock) 
      { 
       if (_Disposed) 
        return; 

       // Completion was already signaled in the constructor 
       _Completion.Reset(); 

       // Tell the message loop thread to quit 
       _ApplicationContext.ExitThread(); 

       // Wait for thread resources cleaning 
       _Completion.WaitOne(); 

       _Disposed = true; 
      } 
     } 

     #endregion // Inquiry 

     #region Event handlers 

     void CurrentDomain_ProcessExit(object sender, EventArgs e) 
     { 
      lock (_Lock) 
      { 
       if (_Disposed) 
        return; 

       // Completion was already signaled in the constructor 
       _Completion.Reset(); 

       // Tell the message loop thread to quit 
       _ApplicationContext.ExitThread(); 

       // Wait for thread resources cleaning 
       _Completion.WaitOne(); 

       _Disposed = true; 
      } 
     } 

     #endregion // Event handlers 

     #region Support 

     public delegate void HandleProvider(IntPtr handle); 
     public delegate bool MessageFilter(ref Message m); 

     internal class CustomMessageFilter : IMessageFilter 
     { 
      private MessageFilter _Filter; 

      public CustomMessageFilter(MessageFilter filter) 
      { 
       _Filter = filter; 
      } 

      #region IMessageFilter Members 

      public bool PreFilterMessage(ref Message m) 
      { 
       return _Filter(ref m); 
      } 

      #endregion // IMessageFilter Members 
     } 

     #endregion // Support 
    } 

    public class WindowHandle : NativeWindow 
    { 
     public WindowHandle() 
     { 
      CreateParams parms = new CreateParams(); 
      CreateHandle(parms); 
     } 
     ~WindowHandle() 
     { 
      DestroyHandle(); 
     } 
    } 
} 

Может использоваться таким образом:

_Completion = new ManualResetEvent(false); 

MessageLoop messageLoop = new MessageLoop(
delegate(IntPtr handle) // Resource initializing 
{ 
    // Sample code, I did this form twain drivers low level wrapping 
    _Scanner = new TwainSM(handle); 
    _Scanner.LoadDs("EPSON Perfection V30/V300"); 
}, 
delegate(ref Message m) // Message filtering 
{ 
    // Asyncrhronous processing of the messages 
    // When the correct message is found --> 
    _Completion.Set(); 
}, 
delegate(IntPtr handle) // Resource cleaning 
{ 
    // Resource cleaning/disposing. In my case, it's the following... 
    _Scanner.Dispose(); 
}, true); // Automatically quit on main application shut-down 

// Anytime you can exit the loop 
messageLoop.ExitLoop(); 
+0

+1 Спасибо за обновление. – Groo

+0

О, спасибо вам! Теперь у меня достаточно очков, чтобы комментировать везде: долгое время я хотел этого :). В моем ответе была ошибка: правильный способ перехвата остановки приложения, когда второй цикл сообщений является единственным потоком, живым (или живым вместе с только фоновыми потоками), регистрируется в событии «AppDomain.CurrentDomain.ProcessExit». Исправлена. – ceztko

Смежные вопросы