ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Using Delegates Asynchronously

by Richard Blewett
02/24/2003

In the article " Understanding the Nuances of Delegates in C#," by Satya Komatineni, the concept of delegates and their general pattern of usage in C# was introduced. This article takes the subject further and looks at another feature of delegates--the built-in ability to perform tasks asynchronously by handing off work to the system thread pool. To understand exactly how delegates perform their async magic, an understanding of how the system thread pool works is first required. For each process in which it is loaded, the CLR manages a thread pool. This thread pool consists of a queue to which pieces of work are added, and a pool of threads that service the queue (by dequeuing the pieces of work). The thread pool is exposed to the world via the System.Threading.ThreadPool class.

The number of threads in the pool is not fixed but gradually grows and shrinks, according to certain heuristics. Initially at 0, the pool grows as the number of concurrent, outstanding pieces of work grows. As threads stand idle, they may be removed from the pool. The pool is capped at 25 threads and this figure is not alterable from managed code. Applications hosting the CLR can change it via the .NET unmanaged API. For example, the maxWorkerThreads attribute in the processModel element in machine.config specifies the maximum size of the thread pool in the ASP.NET worker process.

Thread pool threads have a number of specific characteristics:

If any of the above characteristics are required, create a new thread using the System.Threading.Thread class. However, the thread pool itself is not the focus of this article; rather, how delegates can be leveraged to have work processed by the thread pool is.

Delegates Under the Covers

So what happens when a delegate type if declared? Consider the following delegate declaration.

Code Example 1

delegate long CalcNthPrimeNumber( int N );

This can be used for invoking methods that calculate a specific prime number (this can be a very lengthy calculation): The C# compiler generates code equivalent to the following:

Code Example 2

class CalcNthPrimeNumber : System.MulticastDelegate 
 { 
	// ctor omitted

	// Used in synchronous execution 
	public long Invoke( int N ); 
	
	// Async execution methods 
	public IAsyncResult BeginInvoke( int N, 
                                   AsyncCallback callback, 
                                   object asyncState ); 
	public long EndInvoke( IAsyncResult ar ); 
} 

Invoke is the method that the compiler calls during standard synchronous execution. However, the interesting methods for this discussion are BeginInvoke and EndInvoke. BeginInvoke will always return an IAsyncResult and will always have its last two parameters as an AsyncCallback delegate and a System.Object. EndInvoke will always have as its last parameter an IAsyncResult. The rest of the signatures of these two methods are dependent on the signature of the delegate in question. BeginInvoke always takes any parameters passed by value (unadorned) or reference (ref) before the callback delegate. EndInvoke always has the same return type as the delegate signature, and also has any ref or out parameters before the IAsyncResult.

Invoking Delegates Asynchronously

There are four patterns in async delegate execution: Polling, Waiting for Completion, Completion Notification, and "Fire and Forget". Before explaining the patterns, however, there is one caveat about async delegate execution. You cannot execute a multicast delegate (one that calls more than one client) asynchronously. Each client must be called asynchronously in turn by processing the stored list of clients, the Invocation List. Failing to do this results in an ArgumentException being thrown. Processing the Invocation List turns out to be a far from onerous task--below is example code:

Code Example 3

CalcNthPrimeNumber c; 
	
// Code omitted when numerous clients 
// are hooked up to the delegate 

	
if( c != null ) 
{ 
	foreach( CalcNthPrimeNumber cpn in c.GetInvocationList()) 
	{ 
		// Now call each cpn asynchronously 
	} 
}

Spawning a method onto a worker thread is achieved by calling BeginInvoke on a delegate that wraps the method. This tells the delegate to queue the method invocation onto the Thread Pool. BeginInvoke takes all unadorned and ref parameters, as well as an optional AsyncCallback and System.Object (both of which will be explained later).

Once a method has been given to a worker thread, the thread that instigated the async call carries on with its own work. Given this, how does it find out what the async method returned?

Harvesting Results

Retrieving results is a simple operation; all that is required is a call to EndInvoke. However, there is an issue: if the async method has not completed, EndInvoke will block until it completes. This may be the required behavior--that the caller gets on with other work and then calls EndInvoke when the results are needed. However, what if the caller can usefully get on with work until the async call has completed? Calling EndInvoke before the call is complete means the thread is blocked where it could still be carrying out useful work. Also, what if the caller wanted to be able to timeout the wait after a specified period, that the background task had been too lengthy? EndInvoke blocks until the async method completes--an indeterminate point in time.

We saw in Code Example 2 that BeginInvoke returns an IAsyncResult reference. The semantics of EndInvoke require that it must be passed an IAsyncResult reference that matches that returned by the corresponding BeginInvoke. This is because the asynchronous call is represented by a call object that the IAsyncResult references.

Polling

To find out whether a call has completed, we can talk to the call object representing the async call. Let's look at the definition of IAsyncResult:

Code Example 4

public interface IAsyncResult 
{ 
	object AsyncState{ get; } 
	WaitHandle AsyncWaitHandle { get; } 
	bool CompletedSynchronously { get; } 
	bool IsCompleted { get; } 
}

As can be seen, one of the members of IAsyncResult has been highlighted. IsCompleted returns true when the asynchronous is complete; until that time it returns false. IsCompleted can, therefore, be used to assess whether the long-running calculation is finished. In Code Sample 5, the 672nd prime number is being requested and the calculation will be performed on a thread pool thread. The main thread then polls until the calculation is complete.

Code Example 5

void SpawnPrimeNumberCalc(int N) 
{ 
	CalcNthPrimeNumber cpn = 
          new CalcNthPrimeNumber(CalculatePi); 
	
	IAsyncResult ar = cpn.BeginInvoke( 672, 
                              null, 
                              null ); 

	// Do some stuff 
	while( !ar.IsCompleted ) 
	{ 
		// Do some stuff 
	} 
	
	// we now know that the call is 
  // complete as IsCompleted has 
	// returned true 
	long primeNumber = cpn.EndInvoke(ar); 
} 
	
void CalculatePi(int n) 
{ 
	// calculate the prime number specified by n 
}

Waiting with Timeout

As stated before, EndInvoke is a blocking operation. The calling thread will not continue until the async call has completed. However, what if instead of calculating the Nth prime number locally, the target method were to invoke a web service to perform the calculation? This could involve a network hop (which could have timeouts in the order of minutes, in the case of network failure). It may be preferable to be able to enforce a more user-friendly timeout, say, twenty seconds. Through the use of timers and IAsyncResult.IsCompleted this could be achieved, but it is not necessarily the most efficient way of doing it. Let's have another look at IAsyncResult:

Code Example 6

public interface IAsyncResult 
{ 
	object AsyncState{ get; } 
	WaitHandle AsyncWaitHandle { get; } 
	bool CompletedSynchronously { get; } 
	bool IsCompleted { get; } 
}

This time another of the properties of IAsyncResult has been highlighted: AsyncWaitHandle. This property returns a WaitHandle--an object that can be waited on using the WaitHandle.WaitOne method, which takes a timeout as a parameter. It is, therefore, possible to spawn the async call on to another thread and then wait for it to complete--but then timeout the call if it carries on too long. The code in Code Sample 7 demonstrates this pattern:

Code Example 7

void SpawnPrimeNumberCalc(int N) 
{ 
	CalcNthPrimeNumber cpn = new CalcNthPrimeNumber(CalculatePi); 
	
	IAsyncResult ar = cpn.BeginInvoke( 672, null, null ); 
	
	// Do some stuff 
	
	if( ar.AsyncWaitHandle.WaitOne( 20000, true )) 
	{ 
		long primeNumber = cpn.EndInvoke(ar); 
		Console.WriteLine( "672nd Prime Number is: {0}", primeNumber ); 
	} 
	else 
	{ 
		Console.WriteLine( "Timed out calculating prime number"); 
	} 
} 
	
void CalculatePi(int n) 
{ 
	// calculate the prime number specified by using 
	// a web service 
 }

The highlighted code waits for 20 seconds for the method to return; if it doesn't return in that time, a message to that effect is printed out to the console window. The final facility allows for the calling thread to be completed disengaged from collecting the values returned from an async call. Instead, we can request to be notified when the call completes.

Completion Notification

So far, the last two parameters of BeginInvoke have been passed as null: the AsyncCallback delegate and the System.Object for the async state. These two parameters are used to provide a callback mechanism that will be invoked when the call completes. This is the definition of the AsyncCallback delegate:

delegate void AsyncCallback( IAsyncResult ar );

Therefore, this is the signature of the method that will receive notification of call completion. This method is wrapped in an AsyncCallback delegate instance and passed to BeginInvoke.

Code Example 8

static void Main() 
{ 
	CalcNthPrimeNumber cpn = new CalcNthPrimeNumber(CalculatePi);
	
	IAsyncResult ar = cpn.BeginInvoke( 672, 
                              new AsyncCallback(MyCallback), 
                              null ); 
	
	// Do some stuff 
} 
	
void MyCallback( IAsyncResult ar ) 
{ 
	// Details to follow shortly 
} 
	
void CalculatePi(int n) 
{ 
	// calculate the prime number specified by n 
}

MyCallback will now be called on completion of the CalcNthPrimeNumber invocation. However, to do anything useful, MyCallback needs to be able to call EndInvoke on the CalcNthPrimeNumber delegate, but all it has access to is the IAsyncResult. Taking one final look at IAsyncResult:

Code Example 9

public interface IAsyncResult 
{ 
	object AsyncState{ get; } 
	WaitHandle AsyncWaitHandle { get; } 
	bool CompletedSynchronously { get; } 
	bool IsCompleted { get; } 
}

we see that it has a property, AsyncState, that returns a System.Object. This is the object passed as the last parameter of BeginInvoke. So amending the previous code, we can call EndInvoke as follows:

Code Example 10

static void Main() 
{ 
	CalcNthPrimeNumber cpn = new CalcNthPrimeNumber(CalculatePi); 
	
	cpn.BeginInvoke( 672, new AsyncCallback(MyCallback), cpn ); 
	
	// Do some stuff 
} 
	
void MyCallback( IAsyncResult ar ) 
{ 
	CalcNthPrimeNumber cpn = (CalcNthPrimeNumber)ar.AsyncState; 

	
	long result = cpn.EndInvoke(ar); 
	
	Console.WriteLine("The result is {0}", result ); 
} 
	
void CalculatePi(int n) 
{ 
	// calculate the prime number specified by n 
}

Alternatively, it is documented that the IAsyncResult is passed to the MyCallback is, in fact, referencing an object of type AsyncResult, and so the following code is guaranteed to not throw an InvalidCastException (at least under the version of the framework current at the time of writing--1.0.3705).

Code Example 11

void MyCallback( IAsyncResult iar ) 
{ 
	AsyncResult ar = (AsyncResult)iar; 
	CalcNthPrimeNumber cpn = 
          (CalcNthPrimeNumber)ar.AsyncDelegate; 
	
	long result = cpn.EndInvoke(ar); 
	Console.WriteLine("The result is {0}", result ); 
}

However, casting an interface to a concrete class doesn't sit well with the author's delicate style sensibilities.

Whichever mechanism is used, there are three caveats:

  1. The delegate instance that EndInvoke is called on must be the same instance on which BeginInvoke was called. Strangely, this restriction is not applied uniformly for delegates. For example, when removing event handlers, the delegate just has to refer to the same target. It is not obvious to the author why this is necessary; however, both approaches shown conform to this restriction.
  2. MyCallback will not be executed on the same thread as Main. This means that the class implementing these must be protected using appropriate synchronization primitives if any shared state is updated in the methods.
  3. If the async method has been spawned from a Windows Forms application, then MyCallback is not allowed to touch the UI and must use Control.InvokeRequired and Control.Invoke or Control.BeginInvoke to update UI elements.

Fire and Forget

The Fire and Forget pattern is used when the return value and returned parameters of the call are not required, and when there is no need to synchronize with the asynchronously executing method. In this case, the caller simply needs to call BeginInvoke, passing any normal and ref parameters, and null for the callback and asyncState. For example:

Code Example 12

void SpawnPrimeNumber672Calc() 
{ 
  CalcNthPrimeNumber cpn = 
          new CalcNthPrimeNumber(CalculatePi);

	cpn.BeginInvoke( 672, null, null ); 
	
	// Do some stuff while CalculatePi is executing 
} 
	
void CalculatePi(int n) 
{
	// calculate the prime number specified by n 
}

This pattern is excellent where the requirement is solely to execute the functionality on a separate thread so that the primary thread can resume its main task, having no regard to the asynchronously executing method.

This has been a commonly used pattern. However, there has been a documentation change in version 1.1 of the .NET Framework that has big ramifications for this technique of making async calls. The documentation now states that EndInvoke must be called for a corresponding BeginInvoke--otherwise Microsoft say they may now, or in the future, leak resources. It appears that no resources are leaked under version 1.0 of the framework; however, with this type of warning in place, it is recommended that a call to EndInvoke be made even if the return values of an async call are not required. It is relatively straightforward to create a helper class that handles this for you--one example can be found here.

Conclusion

As can be seen, delegates provide a rich programming model for running code asynchronously. As the async methods run on thread pool threads, there are certain tasks for which async delegates are not appropriate. However, for general-purpose asynchronous work, async delegates are the ideal solution.

Richard Blewett is a UK based independent consultant specializing in .NET technologies.


Return to ONDotnet.com

Copyright © 2009 O'Reilly Media, Inc.