Как программно выйти из второго цикла сообщений?

Я пытаюсь создать второй цикл сообщений для асинхронной обработки / фильтрации сообщений низкого уровня в 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
}

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

_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();
}

РЕДАКТИРОВАТЬ: полное решение в моем ответе.

2 ответа

Решение

Вы должны пройти 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);

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

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

Так как скрытая форма не связана с контекстом, вам нужно выйти из них обоих через некоторое время.

В конце концов я понял, что 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();
Другие вопросы по тегам