Send or post a message to a Windows Forms message loop

26,540

Solution 1

In WinForms you can achieve this with Control.BeginInvoke. An example:

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));
   }
}

A cleaner variant of the above which doesn't depend on WinForms would be to use SynchronizationContext. Call SynchronizationContext.Current on your main thread, and then pass that reference to the constructor of the class shown below.

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);
        }
    }

Solution 2

PostMessage (and likewise SendMessage) are Win32 API functions, and thus are not directly associated with .NET. .NET does however have good support for interoping with the Win32 API, using P/Invoke calls.

As it seems you are new to doing Win32 programming .NET, this MSDN Magazine article provides a solid introduction on the topic.

The excellent pinvoke.net website details how to use many of these API functions from C#/VB.NET. See this page for PostMessage specifically.

The standard declaration is the following:

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

But as the page indicates, it is wise to wrap this function to handle Win32 errors properly:

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

Solution 3

Are you actually wanting to post a message to the message loop or are you simply wanting to update some control in your Form, display a message box, etc.? If it's the former, then refer to @Noldorin's response. If it's the latter, then you need to use the Control.Invoke() method to marshal the call from your "reading" thread to the main UI thread. This is because controls can only be updated by the thread they were created on.

This is a pretty standard thing in .NET. Refer to these MSDN articles to get the basics:

Once you understand how to do this, refer Peter Duniho's blog for how to improve on the canonical technique.

Share:
26,540
dan-gph
Author by

dan-gph

Updated on March 06, 2020

Comments

  • dan-gph
    dan-gph about 4 years

    I have a thread that reads messages from a named pipe. It is a blocking read, which is why it's in its own thread. When this thread reads a message, I want it to notify the Windows Forms message loop running in the main thread that a message is ready. How can I do that? In win32 I would do a PostMessage, but that function does not seem to exist in .Net (or at least I could not find it).

  • dan-gph
    dan-gph over 14 years
    I don't necessarily want to use PostMessage. Is there no plain .Net way of doing what I want to do?
  • Noldorin
    Noldorin over 14 years
    Mike is correct. You are using the Windows message loop, which is based on the Win32 API, and thus requires P/Invoke.