AddThis Social Bookmark Button

Print

Using Delegates Asynchronously
Pages: 1, 2

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