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

dan*_*gph 16 c# multithreading messages winforms

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

Nol*_*rin 18

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

由于您似乎不熟悉Win32编程.NET,因此本MSDN杂志文章提供了有关该主题的可靠介绍.

优秀的pinvoke.net网站详细介绍了如何使用C#/ VB.NET中的许多API函数.看到这个页面PostMessage具体.

标准声明如下:

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

但正如页面所示,最好包装此函数以正确处理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());
    }
}        
Run Code Online (Sandbox Code Playgroud)


Wim*_*nen 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));
   }
}
Run Code Online (Sandbox Code Playgroud)

将使用不依赖于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);
        }
    }
Run Code Online (Sandbox Code Playgroud)


Mat*_*vis 5

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

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

了解了如何执行此操作后,请参考Peter Duniho的博客,以了解如何改进规范技术。