16

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

4

3 回答 3

18

PostMessage(同样SendMessage)是Win32 API 函数,因此与 .NET 没有直接关联。然而,.NET 对使用 P/Invoke 调用与 Win32 API 进行互操作有很好的支持。

由于您似乎是对 .NET 进行 Win32 编程的新手,这篇 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);

但正如页面所示,包装此函数以正确处理 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());
    }
}        
于 2009-09-30T13:03:10.427 回答
17

在 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);
        }
    }
于 2009-09-30T13:55:14.273 回答
5

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

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

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

于 2009-09-30T13:49:03.513 回答