将消息发送或发布到Windows窗体消息循环

问题描述 投票:16回答:3

我有一个线程从命名管道读取消息。这是一个阻塞读取,这就是为什么它在自己的线程中。当此线程读取消息时,我希望它通知在主线程中运行的Windows窗体消息循环消息已准备好。我怎样才能做到这一点?在win32中我会做一个PostMessage,但该功能似乎不存在于.Net(或者至少我找不到它)。

c# winforms multithreading messages
3个回答
16
投票

在WinForms中,您可以使用Control.BeginInvoke实现此目的。一个例子:

public class SomethingReadyNotifier
{
   private readonly Control synchronizer = new Control();

   /// <summary>
   /// Event raised when something is ready. The event is always raised in the
   /// message loop of the thread where this object was created.
   /// </summary>
   public event EventHandler SomethingReady;

   protected void OnSomethingReady()
   {
       SomethingReady?.Invoke(this, EventArgs.Empty);
   }

   /// <summary>
   /// Causes the SomethingReady event to be raised on the message loop of the
   /// thread which created this object.
   /// </summary>
   /// <remarks>
   /// Can safely be called from any thread. Always returns immediately without
   /// waiting for the event to be handled.
   /// </remarks>
   public void NotifySomethingReady()
   {
      this.synchronizer.BeginInvoke(new Action(OnSomethingReady));
   }
}

上面不使用WinForms的更清晰的变体是使用SynchronizationContext。在主线程上调用SynchronizationContext.Current,然后将该引用传递给下面显示的类的构造函数。

public class SomethingReadyNotifier
{
    private readonly SynchronizationContext synchronizationContext;

    /// <summary>
    /// Create a new <see cref="SomethingReadyNotifier"/> instance. 
    /// </summary>
    /// <param name="synchronizationContext">
    /// The synchronization context that will be used to raise
    /// <see cref="SomethingReady"/> events.
    /// </param>
    public SomethingReadyNotifier(SynchronizationContext synchronizationContext)
    {
        this.synchronizationContext = synchronizationContext;
    }

    /// <summary>
    /// Event raised when something is ready. The event is always raised
    /// by posting on the synchronization context provided to the constructor.
    /// </summary>
    public event EventHandler SomethingReady;

    private void OnSomethingReady()
    {
        SomethingReady?.Invoke(this, EventArgs.Empty);
    }

    /// <summary>
    /// Causes the SomethingReady event to be raised.
    /// </summary>
    /// <remarks>
    /// Can safely be called from any thread. Always returns immediately without
    /// waiting for the event to be handled.
    /// </remarks>
    public void NotifySomethingReady()
    {
        this.synchronizationContext.Post(
                state => OnSomethingReady(),
                state: null);
        }
    }

18
投票

PostMessage(以及SendMessage)是Win32 API函数,因此不直接与.NET相关联。但是,.NET确实支持使用P / Invoke调用与Win32 API进行交互。

由于您似乎不熟悉Win32编程.NET,因此this MSDN Magazine article对该主题提供了扎实的介绍。

The excellent pinvoke.net website详细介绍了如何使用C#/ VB.NET中的许多API函数。特别是See this pagePostMessage

标准声明如下:

[DllImport("user32.dll", SetLastError = true)]
static extern bool PostMessage(HandleRef hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

但正如页面所示,最好包装此函数以正确处理Win32错误:

void PostMessageSafe(HandleRef hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
    bool returnValue = PostMessage(hWnd, msg, wParam, lParam);
    if(!returnValue)
    {
        // An error occured
        throw new Win32Exception(Marshal.GetLastWin32Error());
    }
}        

4
投票

您是否真的想要将消息发布到消息循环中,或者您只是想更新表单中的某些控件,显示消息框等?如果是前者,请参阅@ Noldorin的回复。如果是后者,那么您需要使用Control.Invoke()方法来编组从“读取”线程到主UI线程的调用。这是因为控件只能由创建它们的线程更新。

这是.NET中非常标准的东西。请参阅这些MSDN文章以获取基础知识:

一旦你理解了如何做到这一点,请参考Peter Duniho's blog如何改进规范技术。

© www.soinside.com 2019 - 2024. All rights reserved.