BeginInvoke – the land of confusion

February 12, 2007

Over the last couple of weeks, I’ve had to do a few recruiting interviews for Lab49: It’s been illuminating. Delegates are always a good things to chat about and usually, a good candidate will easily rattle off what Invoke and BeginInvoke are and what they do. Sometimes you even get an explanation about how BeginInvoke uses a thread allocated from the ThreadPool. So how does BeginInvoke for a form or controls work? “Oh it works the same doesn’t it? Ooooo, not too sure now you mention it”.

Despite Invoke and BeginInvoke having the same signatures for Controls and delegates, they work very differently.

Delegate Invoke
The method referred to by the delegate instance is called on the target object (if there is one), and the result is returned.

Delegate BeginInvoke
This uses a thread allocated from the ThreadPool to execute the target method. So although the thread creation time is generally fast, there are only 25 threads (by default) per CPU so don’t go hogging them for time consuming stuff.

Control/WinForm BeginInvoke
This doesn’t use a thread to execute the referenced delegate. Instead it uses good old PostMessage to put a registered message on the windows message queue. It’s then picked up by the applications message pump and the target will then be execute on the main GUI thread. Remember that PostMessage returns immediately the message has been put on the queue – that’s how it make the async call.

Control/WinForm Invoke
Those of you still listening, may assume Control Invoke therefore uses SendMessage to make the synchronous call. But no. A little investigation reveals that Invoke and BeginInvoke preserve the calling order, so it seems Invoke is doing the same as BeginInvoke except the call is synchronous. Under the hood, Invoke maybe does something like:

object Invoke(Delegate method)
{
  return EndInvoke(BeginInvoke(method, null));
}

This is confirmed by spying the windows message when Invoke and BeginInvoke are used. The mysterious  WindowsForms12_ThreadCallbackMessage registered message appears in message queue in both cases. I guess the motivation for this was being kind to the message pump. Messages will still be pumped while an Invoke is being called.

A word of caution when using Control Invoke/BeginInvoke. Because they both post messages to the message queue, you might expect everything to behave sweetly. However, if you repeatedly call the control delegates quickly, you’ll suddenly find your GUI will be unresponsive and won’t paint. This is because WM_PAINT and WM_TIMER are the amoeba of the windows message world and will always get pushed to the bottom of the message queue. If you have to deal with high data feed rates, use another mechanism to control the frequency they arrive, otherwise you’ll end up with a sticky GUI. 

Here’s an example of a safe control wrapper that uses control delegates. There’s a plethora of these type of classes, but this is my own twist on it. This approach uses BeginInvoke when called from the non-GUI thread. It results in the GUI being generally more responsive because it’s not having to wait for the the delegate to respond. I also use a technique I’ve called self-referencing delegates. When an invoke is required, the code creates a new delegate to itself and BeginInvokes it. When the same referenced method gets called in the main GUI thread, the base method is called.  

public class SafeTextBox : TextBox
{
  delegate void Set(string text);
  delegate string Get();
  override public string Text
  {
     set
     {
        if (InvokeRequired)
        {
           Set setDelegate = delegate(string text) {Text = text;};
           BeginInvoke(setDelegate, new object[] {value});
        }
        else
           base.Text = value;
     }
     get
     {
        if (InvokeRequired)
        {
           Get getDelegate = delegate() {return Text;};
           return (string)EndInvoke(BeginInvoke(getDelegate, null));
        }
        else
           return base.Text;
     }
  }
}

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: