Update: See at the end of this post for an important issue you need to know about if you're going to use this solution.
Here's something I've been using in my programs lately. I've been pretty darn annoyed with the whole threading issue in Winforms - that is - lack of property semantics to take care of it. So, I created a hack, It's a little ugly, but it lets me write much less code to make my GUI methods in forms thread safe (UI thread safe, I mean).
The main problem is that when you invoke a method on a form that deals with the GUI on that form (changes text or does something to a visual property of a control) it may work and it may not, mostly not. It may hang in a weird way or any million other weird things might happen. In fact, in .NET 2.0 you'll be getting an *exception* if a thread other that the UI thread will be invoking some UI action, so this needs to be taken care of.
Here's how you take care of it today:
Assume the method:
private void EnableButtons(bool enabled)
{
cmdDoSomething.Enabled = enabled;
cmdStop.Enabled = !enabled;
}
If you get a non GUI thread to run this method your application might hang and die. So what you do is this:
private delegate void enableButtonsDelegate(bool enabled);
private void EnableButtons(bool enabled)
{
if(InvokeRequired)
{
Invoke(new enableButtonsDelegate(EnableButtons),enabled);
return;
}
cmdDoSomething.Enabled = enabled;
cmdStop.Enabled = !enabled;
}
that's 4 ugly lines of code you have to write. And you need to create such a delegate for each special method signature you might want to make thread safe (the alternative is to have a standard on thread safe methods and use the same delegate type each time, but that also is pretty ugly and hard to maintain.
So, here's my way:
private void EnableButtons(bool enabled)
{
if(Safe.Invoke(this,enabled)
return;
cmdDoSomething.Enabled = enabled;
cmdStop.Enabled = !enabled;
}
Done.
Here are some other use cases for this:
public void ThreadSafeEmptyMethod()
{
if(Safe.Invoke(this))
return;
//do whatever GUI things you want here!
}
public void ThreadSafeMethodWithParams(int i, string str)
{
if(Safe.Invoke(this,i,str))
return;
//do whatever GUI things you want here!
}
public int ThreadSafeMethodWithParamsAndReturnValue(int i, string str)
{
if(InvokeRequired)
return Safe.InvokeAndReturn(this,i,str);
//do whatever GUI things you want here!
TextBox1.Text = "whatever";
return 1;
}
Under the covers there is a little reflection going on a little ugly trick in the Safe class, but the main thing to know is that when you're dealing with a different thread, performance is not really the issue here anyway, plus, it works great and you don't really notice anything in the UI (I should know - the new Regulator 2005 is using this stuff all over).
The class itself is pretty small (the solution is quite simple), but I have a whole other story about making this work *the hard way* which I'll tell when I get more time 9you *did notice I have much less time than I used to, right?;) )
Update:
Stephen Toub has alerted me to an interesting issue one should consider when using this technique which I had not considered:
Inlining by the compiler. This issue might occur if your "thread safe" method is very small (couple of lines or so). Running in debug mode the code will work nicely, but there is cause for concern that when running is release mode the compiler might "inline" method calls so that "Safe.Invoke" might actually try to invoke a method that happened "before" the actual "thread safe method" such as this. A short example:
" class Program
{
static void Main(string[] args)
{
DoSomething1();
}
static void DoSomething1()
{
DoSomething2();
}
static void DoSomething2()
{
Console.WriteLine(Safe.getOriginalCallingMethod().Name);
}
...
}
Put this into a test.cs file. First compile it with:
% csc.exe /o- /debug+ test.cs
Run test.exe and you’ll get the output you expect:
DoSomething2
Now compile it with:
% csc.exe test.cs
Run test.exe and you’ll get:
Main
If you really want to do this, you’ll need to tell the JIT not to inline the relevant methods using MethodImplOptions.NoInlining. That, of course, is its own perf hit."
Note that this might never occur for you in the program. If you do get a problem, simply out the NoInlining option attribute on the method you are having problems with and that should solve it.
Update 2:
"So", I replied with an email to Stephen, "couldn't regular "Invoke" calls like we used to do *before* my amazing UISafeInvoker class be inlined just as well?" (The second code snippet in this post shows this method)
to which Stephen promptly replied with an email which was interesting and important so I got permission to post the answer here, provided that I explain that this was written "ad-hoc" and may not have been thought out all through.
Here it is:
"Inlining can certainly occur, but that only affects the call site. The target method still exists in the assembly, it’s just that when the JIT generates code for a call site to the method (and it can decide on a call site by call site basis whether to inline, even if it’s the same target method), it might decide to inline it, in which case the generated machine code is inlined into the caller (in addition to any other places it might be). That’s why inlining is only done for smaller functions... inlining can improve perf by removing the overhead associated with calling a method, but it also increases the working set size, which is why the JIT needs all these heuristics to guess when it’s a good time or not. Visual C++ 2005 takes advantage of PGO to use runtime probes to determine what functions should be inlined and where, rather than having to make these sorts of guesses based purely on compile time information. Theoretically, a JIT compiler can re-compile a method when it finds it’s made a poor choice in terms of inlining, but the current version does not do that. In any event, since inlining only affects a call site and not the target method itself, it’s fine to use Control.Invoke to invoke the method, even if it might have been inlined elsewhere. The problem you’re running into is that you’re explicitly looking at a stack trace... inlined methods won’t show up as themselves in a stack trace, since they were never called but rather had their code inlined into the caller. So if you have runtime behavior that’s based on calling methods included in a stack trace, you’ll run into problems when those methods are inlined."
So there :)
This should make for a nice article....