Wednesday, February 28, 2007

Invoke This !

Often when you're multi-threading you'll want to invoke back to another thread (usually the main thread in your program). This isn't very hard to do, and often leads to code like this;

this.Invoke(new DisplayProgressDelegate(this.DisplayProgress), percentageComplete);

This is fine, but there are a few occasions when this is annoying;

1. Sometimes you may not be sure if you were called from another thread or not. Perhaps you are sometimes called by your own code and sometimes called from an event or via a callback that may or may not be on a background thread. If you're not on another thread, you may not want the overhead of the delegate creation/invoke call.

2. It's a lot more typing that just

DisplayProgress(percentageComplete);

3. It looks untidy, especially if repeated often in your code.

4. You may be about to call someone else's code, either through a call back or an event, and you are unsure if they are multi-threading aware, i.e you're about to raise an event from a background thread and untrusted 3rd party plug-ins may or may not try to access user-interface objects in their event handlers and crash as a result. You'd like protect yourself from this and avoid the problem even if the 3rd party coders didn't do the right thing.

The good news is; there is a fiendishly simple way to overcome these issues. Let's say you want to be able to call a method called DisplayProgress and have it invoked only if needed. All you need to do is alter your DisplayProgress routine so it looks like so;

public void DisplayProgress(int percentageComplete)
{
if (myMainForm.InvokeRequired)
myMainForm.Invoke(new DisplayProgressDelegate(this.DisplayProgress), percentageComplete);
else
{
//TODO: Put your normal code for this procedure here.
}
}

and then you can call it from anywhere, like normal;

DisplayProgress(50);

Ok, let's examine what's going on here. The first line in the DisplayProgress method checks to see if you're on the right thread by calling InvokeRequired on a form (which was presumably created on your user interface thread).

If you're on the same thread as the form and no invoke is required, the else condition is executed and no invoke occurs.

If you're on a different thread, the function invokes itself (recursion is cool :) ). When the method enters itself for the second time, it's on the right thread so the else condition executes. When the code is finished the function returns to it's previous call (the invoke), which has no code following it because all the work is done in the else condition, so the method simply ends and returns to the calling procedure.

The end result is that it doesn't matter which thread you call the method from, it will always execute on the thread that myMainForm was created on.

This can be used for any method, including OnEventName methods that raise events, ensuring that everyone who receives the event is running on the thread that is safe for user-interface access, protecting newbie developers writing plug-ins for your code from the hairy world of multi-threading.

There are a few variations on this syntax. You can, for example, create the delegate pointing to the method as a module level variable and re-use the same instance on each call, which reduces the number of objects created/collected by the .NET framework and gives you a small performance boost and reduces memory pressure if the function may be called many times in quick succession.

One caveat using this code; this code assume that you only ever want to cast to a single thread (the one that myMainForm was created on in this example). If you wanted to cast to different threads then you'd need to pass in a form or control object to the method and use it's InvokeRequired/Invoke members.

No comments:

Post a Comment