Introduction
In any reasonably large application, there are a lot of times when you need to perform a quick task asynchronously. One use off-hand is an application that saves a log or status report to file on start-up. The operation is slow in relative terms since it involves file I/O, and we don't have to worry much about other code paths depending on it. Making it asynchronous will speed up our start time with no risk of side effects.
The .NET Framework makes asynchronous processing quite easy, we'll show a few common ways to accomplish it. However there are 'gotchas', and keeping track of everything can lead to subtle mistakes and messier code, so I'll introduce a helper class to make our lives easier.
Background
I'm a sucker for clean and beautifully groomed code. In my experience, maintaining a large code base with few resources is in fact the only way to keep sanity and keep from sliding into a pit of spaghetti. I've been on a push to find areas of necessarily messy or repeated code and find ways to make them more elegant and maintainable - this is the first in a series of small classes and helpers that I kick myself for not doing sooner.
The Obvious and Ubiquitous Solutions
Let's say we have a method as such that we need to run asynchronously:
private void SaveReport() {
statusReport.Save();
Console.WriteLine("Status report saved!");
}
The first thing many people think of is to create a thread as shown below. Besides the chattiness here, this is not the most efficient way to get this done, unless you have a pretty long running task. For every action, a new thread with context and stack space will be created, just to be used for a short period.
ThreadStart ts = new ThreadStart(SaveReport);
Thread t = new Thread(ts);
t.IsBackground = true;
t.Name = "MyThread";
t.Start();
It is more effective to utilize the .NET ThreadPool
, and let it reuse a few pre allocated threads on your behalf. One way to use the ThreadPool
in a round-about way is to make a delegate and begin invocation on it directly.
MethodInvoker dlg = new MethodInvoker(SaveReport);
dlg.BeginInvoke(null, null);
There is a problem with both of those examples as well. The BeginInvoke
method takes two parameters, an AsyncCallback
for notifying when the operation completes, and an object that is passed through to that callback for the callers purposes, typically the original delegate. Now creating a callback just adds more trouble to this whole exercise, particularly if you do not need to know exactly when the operation completes. However, although not documented well, I've seen issues if the callback is not used to call EndInvoke
.
The last way I'll show is to go straight after the ThreadPool
.
WaitCallback wc = new WaitCallback(SaveReport);
ThreadPool.QueueUserWorkItem(wc);
This is much more concise, but without the control over the process that we have in the above examples. A larger problem that I haven't discussed is that any of these examples can fail spectacularly, or sometimes silently - any exceptions from your invoked methods will vanish into the ether. Once error handling (and logging, right?) is applied to both the action being performed, and the action of invoking it, and possibly to ending the invoke, we have a lot of code to just make a one-liner action happen safely asynchronous.
If this does fail, you may want to then attempt the action with a dedicated thread to make sure it works if the ThreadPool
is not cooperating, that's more code to bandy about.
A more subtle limitation I started to run into is the matter of reentrance. In the above example, we are periodically saving a status report. What happens if it gets delayed? It's quite possible that the next time you invoke it the prior attempt is still in process, all kinds of mayhem could ensue. Now we would need to add even more code for blocking and thread safety for tasks that might get invoked more than once.
The Solution - Using the Code
What if we had some kind of magical helper class that had the power of any of the examples above, but was also incredibly concise? It would put all of this power in one place, so troubleshooting and changes and error handling and tweaks could be concentrated in one place. Come to think of it, such a class might even heal sick puppies! Here's how we would use it.
Async.Do(SaveReport);
What? How could it be that easy? Is this possible? Yes, all this can be yours, and more. .NET automatically takes any void
method with no parameters and invisibly makes a delegate
for it, which is how the above works by passing a method name, we can also pass delegate
s or anonymous methods.
Async.Do(delegate {
Console.WriteLine("I'm in an anonymous delegate!");
statusReport.Save();
});
That saved us from even having to define the first method - just add some code and go.
More Advanced Usage
Now we can get into more functionality, that would take some time to implement for each task normally. With some overloading and fancy footwork, we can also do any of the following..
Async.Do(SaveReport, ReentranceMode.Bypass);
We can also track this task and know when it completes, or use the AsyncResult
to abort it. The Do
method returns a custom class that implements the IAsyncResult
interface, with much more functionality than the typical .NET Framework usages.
IAsyncResult result = Async.Do(SaveReport, true);
if (!result.IsCompleted) result.AsyncWaitHandle.WaitOne();
Console.WriteLine("My result was: " + result.ReturnValue);
Instead of defining a method, save time for small tasks by using an anonymous method.
Async.Do(delegate { return GetAnObject("A string parameter"); });
And finally, the most advanced overload of the 'Do
' method..
Async.Do(
SaveReport,
false,
this,
true,
ReenteranceMode.Stack);
One last example. What if you need to run multiple tasks and know when they are all complete? A good example of this I found was for closing a collection of network listeners, each one could block for a while so we should close them in parallel but wait for them all to complete before exiting the application.
List<waithandle /> wait = new List<waithandle />();
foreach (Listener l in listeners) {
IAsyncResult result = Async.Do(l.CloseNetwork);
wait.Add(result.AsyncWaitHandle);
}
WaitHandle.WaitAll(wait.ToArray());
Points of Interest
First you might wonder how this class works. I didn't want to get too long in this article, but my blog has a follow-up with all the details of how it was implemented, check it out.
I think ReentranceMode
needs a little more attention, as it is a very important feature of this class. The ReentranceMode
enum has three options; Allow, Bypass,
and Stack
.
Allow: In this case, there are no locking checks. If you run a task, and then immediately run it again (possibly from a totally different area of your app) Allow
will let them both go at the same time. Find if you aren't accessing any shared resources, say for logging or the like.
Stack: With stack
mode, the class will detect if you already have a task executing in the same method (including anon. methods/delegates). It will block before the task begins, and continue once the prior copy completes. Good for shared resources like writing to a file, but be careful — if you get too many stacked up it could balloon your memory usage.
Bypass: The most common that I use, bypass
will detect prior instances like stack
does, but if it finds one already executing it dumps out immediately without running. This is good for things like internal checks or other tasks that need to run occasionally, but not necessarily back-to-back.
In Part II we will add on to this Async class giving it the functionality to invoke tasks on to the GUI thread as well, making safe multithreading easy in Windows Forms apps.
This solution provides all the power of any other methodology with none of the bloat and maintainability issues. This class has greatly simplified code flow and literally cut thousands of lines of code from a large application I work with, so I hope it can be of use to you as well!
History
- 25th September, 2008: Initial version
- 1st October, 2008: Added link to second version and expanded Points of Interest