【发布时间】:2010-12-02 15:32:18
【问题描述】:
我有一个从命名管道读取消息的线程。这是一个阻塞读取,这就是它在自己的线程中的原因。当该线程读取消息时,我希望它通知在主线程中运行的 Windows 窗体消息循环消息已准备好。我怎样才能做到这一点?在 win32 中我会做一个 PostMessage,但该功能在 .Net 中似乎不存在(或者至少我找不到它)。
【问题讨论】:
标签: c# winforms multithreading messages
我有一个从命名管道读取消息的线程。这是一个阻塞读取,这就是它在自己的线程中的原因。当该线程读取消息时,我希望它通知在主线程中运行的 Windows 窗体消息循环消息已准备好。我怎样才能做到这一点?在 win32 中我会做一个 PostMessage,但该功能在 .Net 中似乎不存在(或者至少我找不到它)。
【问题讨论】:
标签: c# winforms multithreading messages
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 page 专门用于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());
}
}
【讨论】:
您是真的想向消息循环发布消息,还是只是想更新表单中的某些控件、显示消息框等?如果是前者,请参考@Noldorin 的回复。如果是后者,那么您需要使用 Control.Invoke() 方法将调用从“读取”线程编组到主 UI 线程。这是因为控件只能由创建它们的线程更新。
这是 .NET 中非常标准的事情。请参阅这些 MSDN 文章以了解基础知识:
一旦您了解如何执行此操作,请参阅 Peter Duniho's blog 了解如何改进规范技术。
【讨论】:
在 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);
}
}
【讨论】: