【问题标题】:How to programmatically exit from a second message loop?如何以编程方式退出第二个消息循环?
【发布时间】:2011-06-16 11:41:13
【问题描述】:

我正在尝试创建第二个消息循环以在 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();
}

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

【问题讨论】:

    标签: c# hidden window-handles message-pump message-loop


    【解决方案1】:

    您应该将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 事件时被调用。

    由于隐藏形式与上下文无关,因此您需要在某个时间将它们都退出。

    【讨论】:

    • 否,因为这将显示第二种形式。请注意,第二种形式是假的,应该隐藏起来。初始化它是一种创建将接收消息的窗口句柄的方法。我对该表单的用户迭代不感兴趣。一旦主窗体关闭,我预计第二个应用程序上下文也会关闭。也许循环没有退出,因为表单没有关闭......现在我会尝试。
    • 好的,我明白我在 HandleHelper 中所做的事情在消息循环中注册了“某些东西”,因此尝试运行 ApplicationContext.ExitThread 不确定地等待。将尝试了解该“某事”是否可以取消注册。
    • 是的,您需要查看您正在使用的 TwainSM 类的文档。如果它实现了IDisposable,你可以在进程完成后尝试处理它(但这只是一个想法)。
    • 大声笑,TwainSM 如此糟糕,你无法想象(这是我决定使用第二个消息循环运行它的主要原因)。无论如何,它有一个析构函数,它负责释放资源。非常接近全面的解决方案。
    • 我最终意识到thread.IsBackground = true; 并没有那么糟糕。我发布了我的综合解决方案。感谢您指出我坚持使用 ApplicationContext.ExitThread()。我非常接近但不知道很多 Win32 API。标记为解决方案:)
    【解决方案2】:

    我最终意识到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();
            }
        }
    }
    

    可以这样使用:

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

    【讨论】:

      猜你喜欢
      • 2012-05-14
      • 2021-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-12
      • 2012-07-02
      • 1970-01-01
      相关资源
      最近更新 更多