C# Delegates and Events—Asynchronous method calls
Microsoft .NET Framework, ASP.NET, Visual C# (CSharp, C Sharp, C-Sharp) Developer Training, Visual Studio
| CSharp-Online.NET:Articles |
| C# Articles |
|
| edit |
Asynchronous method calls
Another useful feature of delegates is the ability to execute a method asynchronously. That is, through a delegate, you can begin invocation of a method and then return immediately while the delegate executes its method in a separate thread.
The following example demonstrates the use of delegates for asynchronous method invocation. To see this in action we need a method that simulates a lengthy operation, such as flushing data to disk. Below is a class named DataCache, which may periodically need to write its cache to a file. The class contains a method named FlushToDisk to simulate this operation.
class DataCache { public int FlushToDisk(string fileName) { // simulate a long operation Thread.Sleep(4000); return 0; } }
Since writing to disk is an expensive operation, we would like execute the FlushToDisk method asynchronously. The next example defines the CacheFlusher delegate and then uses it to call the DataCache object’s FlushToDisk method asynchronously.
class Program { // define a delegate for the long operation delegate int CacheFlusher(string fileName); static void Main(string[] args) { // create the object and then the delegate DataCache cache = new DataCache(); CacheFlusher flusher = new CacheFlusher(cache.FlushToDisk); // invoke the method asynchronously IAsyncResult result = flusher.BeginInvoke("data.dat", null, null); // get the result of that asynchronous operation int retValue = flusher.EndInvoke(result); Console.WriteLine(retValue); } }
In the first asynchronous example, the Main method creates an instance of DataCache and encapsulates its FlushToDisk method in an instance of the CacheFlusher delegate. At the time our CacheFlusher delegate is declared, the compiler generates two methods for it named BeginInvoke and EndInvoke. These two methods, respectively, are used to kick off a lengthy method call and then retrieve its return value when that method is finished. Rather than invoking the delegate as before, Main executes the delegate as an asynchronous operation by calling its BeginInvoke method.
The generated BeginInvoke method will accept the same argument types as declared in the delegate’s signature, followed by some other arguments that we will ignore for now. The return value of BeginInvoke is a reference to an interface named IAsyncResult which we later pass to EndInvoke to receive the return value of the asynchronous method.
The generated EndInvoke method is defined to return the same type as the asynchronous method. When called, EndInvoke will block until the asynchronous method completes its execution. Therefore, to be efficient, we need to find other strategies for calling EndInvoke that will not block the calling thread. Below, I show three techniques for calling EndInvoke, polling for completion, using a wait handle, and using an AsyncCallback delegate.
First we look at polling. The next code segment uses a while loop to poll the IsCompleted property of the IAsyncResult object. If the method call is not yet complete, then we can perform some other work and check during the next pass through the loop to see if the asynchronous operation has completed.
// begin execution asynchronously IAsyncResult result = flusher.BeginInvoke("data.dat", null, null); // wait for it to complete while (result.IsCompleted == false) { // do some work Thread.Sleep(10); } // get the return value int returnValue = flusher.EndInvoke(result);
The next technique is to block on the IAsyncResult object’s wait handle. When the asynchronous method is complete, the IAsyncResult object’s AsyncWaitHandle will be signaled. Then any thread waiting on that handle will be awakened. Once awake, the thread can call the IAsyncResult object’s EndInvoke method to receive the return value.
// call the delegate asynchronously IAsyncResult result = flusher.BeginInvoke("data.dat", null, null); // wait for the call to finish result.AsyncWaitHandle.WaitOne(); // get the return value int returnValue = flusher.EndInvoke(result);
The final method is to create an instance of the AsyncCallback delegate and pass it to the BeginInvoke method. The AsyncCallback delegate is defined in the System namespace. It accepts an IAsyncResult reference as its only parameter and returns void.
When you pass an AsyncCallback to BeginInvoke, the delegate will invoke that callback upon completion of the asynchronous operation. Then, you can use that opportunity to call EndInvoke to retrieve the return value and perform any resource cleanup, as the next example shows.
static void Main(string[] args) { // create the object DataCache cache = new DataCache(); // create the delegate CacheFlusher flusher = new CacheFlusher(cache.FlushToDisk); // call the delegate asynchronously flusher.BeginInvoke("data.dat", new AsyncCallback(CallbackMethod), flusher); // wait to exit Console.WriteLine("Press enter to exit"); Console.ReadLine(); } static void CallbackMethod(IAsyncResult result) { // get the delegate that was used to call that // method CacheFlusher flusher = (CacheFlusher) result.AsyncState; // get the return value from that method call int returnValue = flusher.EndInvoke(result); Console.WriteLine("The result was " + returnValue); }
If you run this example you will see that the line “Press Enter to Exit” will be displayed in the console window, followed by the output from CallbackMethod. This is because the Main method calls BeginInvoke and continues processing, displaying its own output without waiting for the CacheFlusher delegate to finish. Later, when the asynchronous operation is complete, the delegate calls the CallbackMethod, which writes its output to the screen.
The Main method begins by calling the delegate object’s BeginInvoke method as before. But, this time those second and third arguments are used. The second argument is an instance of another delegate named AsynchCallback. The AsynchCallback delegate is defined to accept an IAsyncResult reference and return void, such as the static CallbackMethod provided as part of the example. When the asynchronous operation is complete, the delegate invokes that callback. It is within the body of that CallbackMethod, the method referenced by the AsynchCallback, that we have the opportunity to call EndInvoke.
Whatever object you pass to the third argument of BeginInvoke will be stored in the AsyncState property of the IAsyncResult reference passed to the CallbackMethod. Notice in the CallbackMethod that the first thing we do is cast the AsyncState property to the CacheFlusher delegate. This is so that we can call the object’s EndInvoke method to receive the return value.
|

