Multithreading: implementing the event based pattern
In the last post of the series, we’ve taken a look at the main features offered by the event based pattern. Today, we’re going to look at how we can implement that pattern. We’re going to start small and we’re going to reuse the IAsyncResult sample for showing how you can implement this pattern.
As we’ve seen in the previous post, we need to (at least) add a method (named XXXAsync) that will start the asynchronous operation and an event (named XXXCompleted) that will fire . In practice, this means that we need something like this:
class DoSomeWork {
public Boolean IsPrimeNumber(Int32 number) {
/* same as before*/
}
public event EventHandler<PrimeNumberVerificationCompletedEventArgs> PrimeNumberCompleted;
protected void OnPrimeNumberCompleted( PrimeNumberVerificationCompletedEventArgs evtArgs ){
if (PrimeNumberCompleted != null) {
PrimeNumberCompleted(this, evtArgs);
}
}
public void IsPrimeAsync(Int32 number) {
//some code here…
}
}
As you can see, we’ve got a PrimeNumberVerificationCompletedEventArgs that is passed back. As we’ve said, this must be a AsyncCompletedEventArgs (or derived) class. In this case, we need to return a value, so we’ll need to create a new derived class which I called PrimeNumberVerificationCompletedEventArgs. Here’s the code for that class:
public class PrimeNumberVerificationCompletedEventArgs: AsyncCompletedEventArgs {
private readonly Int32 _testedNumber;
private readonly Boolean _isPrime;
internal PrimeNumberVerificationCompletedEventArgs( Int32 testedNumber,
Boolean isPrime,
Exception exception,
Boolean calculationCanceled,
Object state )
: base(exception, calculationCanceled, state) {
_testedNumber = testedNumber;
_isPrime = isPrime;
}
public Int32 TestedNumber {
get{
RaiseExceptionIfNecessary();
return _testedNumber;
}
}
public Boolean IsPrime{
get{
RaiseExceptionIfNecessary();
return _isPrime;
}
}
}
As you can see, I’ve added two properties to the base class: TestedNumber (returns the number that was passed to the IsPrimeAsync method) and IsPrime (returns the result of the processing). As you can see, both properties call the RaiseExceptionIfNecessary method before returning the value back to the client. Internally, this method will throw the exception (when it isn’t null) that supposedly originated during the asynchronous operation and that was passed to the constructor.
As you might expect, most of the action happens on the IsPrimeAsync method. From within that method, we need to start the processing on a different thread. For starters, we’re allowing only one asynchronous call at the time. Here’s a possible implementation for the IsPrimeAsync method:
public void IsPrimeAsync(Int32 number) {
if (_isRunning) {
throw new InvalidOperationException();
}
_isRunning = true;
_currentOperation = AsyncOperationManager.CreateOperation(null);
ThreadPool.QueueUserWorkItem(state =>
{
var numberToCheck = (Int32)number;
var isPrime = false;
Exception throwException = null;
try {
if (number > 2) {
isPrime = true;
var half = number / 2;
for (var i = 2; i < half; i++) {
if (number % i == 0) {
isPrime = false;
break;
}
}
}
}
catch (Exception ex) {
throwException = ex;
}
finally {
NotifyEndOfOperation(numberToCheck, isPrime, false, throwException);
}
}, number);
}
There are a couple of interesting points here:
- we start by creating an AsyncOperation instance (_currentOperation is a field of the class) by using the helper AsyncOperationManager class. As you’ll see, this class handles most of the work for us and I’ll have much more to say about it in future posts (I guess that when I finish this topic, I’ll go straight into GUI and multithreading);
- we use an auxiliary field (_isRunning) which is signaled when we start a new operation. Since this first version does not support more than one asynchronous call at a time, we end up throwing an exception whenever the method is called before it ends the “current” running asynchronous operation;
- we need to wrap our code in a try/catch block so that we catch all the exceptions that might happen during the execution of that code (this is not without its problems – what to do with an out of memory exception? – but it ensures that a less “critical” exception doesn’t crash the process). Notice that eventual exceptions are saved on local field so that they can be passed to the client of the event.
The NotifyEndOfOperation method is responsible for “firing“ the event back on the thread that started the request (which is really needed when you’re writing code for GUIs). To do that, it must first pack all the info into a PrimeNumberVerificationEventsArg expected by the consumer of the API. As you’ll see, the method ends up being really simple because we end up relying once again on the “mysterious” AsyncOperation class:
private void NotifyEndOfOperation( Int32 numberToTest,
Boolean isPrime,
Boolean cancelled,
Exception thrownException ){
var evt = new PrimeNumberVerificationCompletedEventArgs(
numberToTest,
isPrime,
thrownException,
cancelled,
null);
_currentOperation.PostOperationCompleted(
state => {
_isRunning = false;
OnPrimeNumberCompleted((PrimeNumberVerificationCompletedEventArgs)state);
},
evt);
}
As I’ve said before, we’ll spend a couple of posts on GUIs and asynchronous processing. Until then, just know that calling this method signals the end of an asynchronous operation (notice also that this means that you cannot make future calls over this AsyncOperation instance). From within the delegate we pass to the method, we turn off our running flag and fire the event by calling the auxiliary OnPrimeNumberCompleted method:
protected void OnPrimeNumberCompleted( PrimeNumberVerificationCompletedEventArgs evtArgs ){
if (PrimeNumberCompleted != null) {
PrimeNumberCompleted(this, evtArgs);
}
}
And now that we’ve got the class ready, here’s how you’re expected to use it:
var work = new DoSomeWork();
work.PrimeNumberCompleted += (sender, e ) => {
Console.WriteLine(e.IsPrime); evt.Set();
};
work.IsPrimeAsync(19);
There still more to say about this pattern, so we’ll return to it in future posts. Keep tuned!