C# 如何以编程方式退出第二个消息循环?

C# 如何以编程方式退出第二个消息循环?,c#,hidden,window-handles,message-pump,message-loop,C#,Hidden,Window Handles,Message Pump,Message Loop,我正在尝试创建第二个消息循环,以便在C#中异步处理/过滤低级消息。它的工作原理是创建一个隐藏表单,公开要挂接的句柄属性,并在单独的线程中运行第二个消息循环。目前我对结果很满意,但我无法正确退出第二个循环。唯一的解决方法是将IsBackground属性设置为true,这样第二个线程将在主应用程序出口处简单终止(不处理所有挂起的消息) 问题是:如何正确退出该消息循环,以便第二个Application.Run()返回?我尝试了不同的方法创建一个单独的ApplicationContext并控制各种事件(

我正在尝试创建第二个消息循环,以便在C#中异步处理/过滤低级消息。它的工作原理是创建一个隐藏表单,公开要挂接的句柄属性,并在单独的线程中运行第二个消息循环。目前我对结果很满意,但我无法正确退出第二个循环。唯一的解决方法是将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
}

编辑:我的答案中的完整解决方案。

您应该将
表单
实例作为参数传递给
应用程序上下文
选择器:

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,但必须通过调用该方法手动关闭上下文。如果在构造函数中传递表单,则当表单触发
FormClosed
事件时,实际上会调用此方法


由于隐藏形式与上下文无关,您需要在某个时候同时退出它们。

我最终意识到
thread.IsBackground=true还不错,因为这是确定“嘿,我是最后一个运行的线程,我应该退出”的唯一方法。仍然需要正确的资源清理,这很难。为此,需要第三个用于资源清理的委托,我刚刚将其注册到AppDomain.CurrentDomain.ProcessExit事件。我甚至为MessageLoop类提供了一个ExitLoop()方法(问题中是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();
        }
    }
}

不,因为这将显示第二种形式。请注意,第二张表格是假的,应该隐藏起来。初始化它是创建接收消息的窗口句柄的一种方法。我对那个表单的用户操作不感兴趣。一旦主窗体关闭,我希望第二个应用程序上下文也会关闭。可能循环没有退出,因为表单未关闭。。。现在我来试试。好的,我知道我在HandleHelper寄存器中所做的是消息循环中的“某物”,所以尝试运行ApplicationContext.ExitThread会无定义地等待。将尝试了解“somenthing”是否可以注销。是的,您需要检查您正在使用的
TwainSM
类的文档。如果它实现了
IDisposable
,您可以在流程完成后尝试处理它(但这只是一个想法)。哈哈,TwainSM太糟糕了,您无法想象(这就是我决定使用第二个消息循环运行它的主要原因)。无论如何,它有一个析构函数,负责释放资源。我最终意识到
thread.IsBackground=true没有那么糟糕。我发布了我的综合解决方案。感谢您指点我坚持使用ApplicationContext.ExitThread()。我离这里很近,但对Win32 API知之甚少。标记为解决方案:)
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();