Update windows forms from another thread


I wrote this in response to a customer question today, and thought it might be interesting:

******

To update something on a form from another thread, you need to use Invoke() to get
to the right thread. If I have a method that's called through an EventHandler delegate,
it will look something like:

public void ButtonClick(object sender, EventArgs e)
{
	// update the form here...
}
    

If this is called through a different thread, and it tries to update the code, bad
things may happen. So, you need to have a way to get there, and that's what Invoke()
does - it arranges for the code to be called on the right thread. The simplest thing
to do is to just use Invoke() to the same function, with a test to see if we're on
the right thread. Windows forms provides the function to do the testing...

public void ButtonClick(object sender, EventArgs e)
{
	if (InvokeRequired)
	{
		Invoke(new EventHandler(ButtonClick), new object[] {sender, e});
	}
	else
	{
		// update the form here...
	}
}
    

That works well, though it is a bit ugly. When you move to the Compact Framework,
things get a bit weird. You can still use Invoke() on the compact framework, but it
has two strange restrictions:

1) You can't pass any parameters to the method, because Invoke() only has one parameter,
the delegate.

2) You need to call through an EventHandler delegate.

You can get around the first one by saving any parameters in a field before the Invoke(),
but it's a bit ugly. The second part is weird because EventHandler has two parameters,
but since you can't specify any parameter values, there's no way to use them.

Comments (9)

  1. Mike Dimmick says:

    With all due respect, I think the Compact Framework team went the wrong way when omitting features. They omitted the complicated methods, not the simple methods that could be implemented by calling the complicated ones.

    I suppose the rationale was to try to get the size down: fewer methods means less metadata (and of course less code). The Windows CE team actually did this quite effectively by removing all the simple line-drawing APIs and leaving in only PolyLine.

    .NET CF includes only Graphics.DrawLine, not Graphics.DrawLines. This leads to abysmal performance if you need to draw a series of connected lines (as someone was complaining on either CodeProject or pocketpcdn.com/forums).

  2. Mike Dimmick says:

    Also, you can’t WaitOne for a period of time using the Compact Framework – you can only block until the WaitHandle is signalled. WaitAll and WaitAny are also not available, despite the underlying OS support for WaitForMultipleObjects.

  3. Frans Bouma says:

    With all due respect, but the ONLY reason why updating a form from another thread goes wrong is because the HDC can be different on another thread, an object that isn’t known in .NET. Now, for a .NET developer, the form created on another thread (thus the form OBJECT) is available to other threads in the same process. Why does the .NET developer KNOW how it is done under the hood? The .NET developer doesn’t understand what ‘HDC’ is, because it’s not part of the .NET framework. So why using Invoke? Because Microsoft says so? That’s not a good reason IMHO. The .NET developer doesn’t know nor SHOULD know that under the hood thread A is sending the WM_PAINT to another thread’s message pump. All the .NET developer should be concerned about is calling an event handler. ‘Invoke’ is therefore an illustration of bad design in the Winforms api: it is build on top of Win32, which can be fine, but it leaks some of the crappy things in Win32 through to .NET, which is bad, because now we never get rid of those bad things.

    Please fix the .NET api so this isn’t necessary anymore, that I don’t have to call Invoke just because under the hood some message has to be send to some other message pump I don’t even know the existence of.

  4. Eric Gunnerson says:

    Frans,

    I’m not an expert on the Windows Forms design, but I think the reason that it’s done the way that it is is performanc. If every call had to check to see whether you were on there right thread, the perf would be reduced even when you were already on the GUI thread.

    I do agree with you about hte current model – I find that having to deal with this in WinForms adds a lot of ugly code that you shouldn’t have to write.

  5. Ilya Ryzhenkov says:

    The cost of doing correct thing here is the following peace of code:

    // dlg is delegate
    ISynchronizeInvoke invoker = dlg.Target as ISynchronizeInvoke;
    if (invoker != null)
    {
    if (invoker.InvokeRequired)
    retval = invoker.Invoke(subscriber,parms);
    else
    retval = dlg.DynamicInvoke(parms);
    }

    or something similar. Deriving from delegate and building this into could be simple, if we could derive from Delegate 🙂

  6. Andrea says:

    Test comment

  7. Curtis Forrester says:

    One thing I notice is that another thread may update the main form with Invoke – i.e., form1 – but same does not work on a form created with ShowDialog(). In that case it always deadlocks. Any suggestions on that?

Skip to main content