Introduction
As you should already now, using Windows.Forms gets really ugly when you need to access the user interface from multiple threads. IMHO, this is an example of leaky abstraction. I don't know and I don't want to know why I can't simply write:
this.text = "New Text";
In any thread, the
Windows.Forms.Control class should abstract me for any threading issue. But it doesn't. I will try to show several ways to solve this problem, and finally the simplest solution I've found. Wait to the end to find the good stuff!
(or click here)
One thing worth knowing: When you run a program with this UI threading issue from within Visual Studio, it will always throw an exception. The same program running as a standalone EXE may not throw the exception. That is to say, the development environment is stricter than the .NET framework. This is a good thing, it is always better to solve problems in development time that have random issues in production.
This is my first article and English is not my language, so please be gentle!
The "Standard" Pattern
I don't know who came out with this code at first, but this is the standard solution for the threading issue:
public delegate void DelegateStandardPattern();
private void SetTextStandardPattern()
{
if (this.InvokeRequired)
{
this.Invoke(new DelegateStandardPattern(SetTextStandardPattern));
return;
}
this.text = "New Text";
}
Good points about this solution:
- It does the job.
- It works with C# 1.0, 2.0, 3.0, 3.5, Standard and Compact Framework (since CF 1.1, you don't have InvokeRequired in CF 1.0).
- Everyone uses it, so when you read something like this, you know this code will be probably called from another thread.
Bad points:
- It's a lot of code for updating a text!
- You need to copy/paste it, you can't make a generic method from it.
- If you need to call a method with parameters, you can't even reuse the delegate. You need to declare another delegate for each different parameter set.
- It's ugly. I know this is subjective, but it is. I especially hate the need to declare a delegate "outside" the method.
There are some clever solutions out there, like this one using AOP, and this one using Reflection. But I wanted something easier to implement. One way to go could be a SurroundWith code snippet, but I like my code issues to be solved by the language, not by the IDE. Also, it will only solve the copy/paste problem, it will still be a lot of code for something that simple.
Why can't we generalize the standard pattern? Because there is no way in .NET 1.0 to pass a block of code as a parameter, because when C# started it has almost no support for a functional programming style.
The "Anonymous delegate" Pattern
With C# 2.0, we get anonymous delegates and the MethodInvoker class, so we could simplify the standard pattern into this:
private void SetTextAnonymousDelegatePattern()
{
if (this.InvokeRequired)
{
MethodInvoker del = delegate { SetTextAnonymousDelegatePattern(); };
this.Invoke(del);
return;
}
this.text = "New Text";
}
This is a slightly better solution, but I've never seen anyone using it.
But what happens if instead of executing this.text = "New Text"; you need to call a method with parameters? Something like:
private void MultiParams(string text, int number, DateTime dateTime);
There is no big deal, since delegates can access outer variables. So, you can write something like this:
private void SetTextDelegatePatternParams(string text, int number, DateTime datetime)
{
if (this.InvokeRequired)
{
MethodInvoker del = delegate {
SetTextDelegatePatternParams(text, number, datetime); };
this.Invoke(del);
return;
}
MultiParams(text, number, datetime);
}
The "Anonymous delegate" pattern can be minimized a lot if you "forget" to ask if invoke is required. That leads us to...
The "Anonymous delegate minimized" Pattern
This is really good:
private void SetTextAnonymousDelegateMiniPattern()
{
Invoke(new MethodInvoker(delegate
{
this.text = "New Text";
}));
}
private void SetTextAnonymousDelegateMiniPatternParams
(string text, int number, DateTime dateTime)
{
Invoke(new MethodInvoker(delegate
{
MultiParams(text, number, dateTime);
}));
}
It works, it's easy to write, it's only a few lines away from perfect. The first time I saw this, I thought that's what I was looking for. So what's the problem? Well, we forgot to ask if Invoke was required. And since this is not the standard way to do it, it will not be clear to others (or to ourselves in a couple of months) why we are doing this. We could be nice and comment the code, but let's be honest, we all know we won't. At least I prefer my code to be more "intention revealing". So, we have...
The "UIThread" Pattern, or the Way I've Solved this Problem
First I show you the rabbit:
private void SetTextUsingPattern()
{
this.UIThread(delegate
{
this.text = "New Text";
});
}
private void SetTextUsingPatternParams(string text, int number, DateTime dateTime)
{
this.UIThread(delegate
{
MultiParams(text, number, dateTime);
});
}
And now I'll show you the trick. It's a simple static class with only one method. It's an extension method, of course, so if you have some objections like "extension methods are not pure object orientated programming" I recommend you to use Smalltalk and stop complaining.
Or use a standard helper class, as you wish.
Without comments, namespace and using, the class looks like this:
static class FormExtensions
{
static public void UIThread(this Form form, MethodInvoker code)
{
if (form.InvokeRequired)
{
form.Invoke(code);
return;
}
code.Invoke();
}
}
As you can see, it is just the standard pattern, as generalized as possible.
Good points about this solution:
- It does the job.
- It works the same with Full and Compact Framework (with just one extra line).
- It's simple (almost looks like a
using{} block!).
- It doesn't care if you have parameters or not
- If you read it again in three months, it will still look clear.
- It uses a lot of what modern .NET has to offer: Anonymous delegates, extension methods, lambda expressions (if you want, see later), generic type inference.
Bad points:
- Er.... waiting for your comments.
Points of Interest
This code needs full .NET Framework 3.5 to work! To make it work in Compact Framework, simply declare MethodInvoker like:
public delegate void MethodInvoker();
You can write even less code using lambda style, if you only need to write one line you can do something as small as:
private void SetTextUsingPatternParams(string text, int number, DateTime dateTime)
{
this.UIThread(()=> MultiParams(text, number, dateTime));
}
and still be clear!
The download is a simple app that shows some wrong code that fails, the "standard pattern", and the "UIThread pattern" I've been talking about, and has two projects, one for full .NET Framework and one for Compact Framework.
History
- 24th June, 2009: Initial version