C# Programmer[ap]s Cookbook

Problem

You need to start execution of a method and continue with other tasks while the method runs on a separate thread. After the method completes, you need to retrieve its return value.

Solution

Declare a delegate with the same signature as the method you want to execute. Create an instance of the delegate that references the method. Call the BeginInvoke method of the delegate instance to start execution of your method. Use the EndInvoke method to determine the method's status as well as obtain the method's return value if complete.

Discussion

Typically, when you invoke a method you do so synchronously, meaning that the calling code blocks until the method is complete. Most of the time, this is the expected and desired behavior because your code requires the operation to complete before it can continue. However, sometimes it's useful to execute a method asynchronously, meaning that you start the method in a separate thread and then continue with other operations.

The .NET Framework implements an asynchronous execution pattern that allows you to call any method asynchronously using a delegate. When you declare and compile a delegate, the compiler automatically generates two methods that support asynchronous execution: BeginInvoke and EndInvoke . When you call BeginInvoke on a delegate instance, the method referenced by the delegate is queued for asynchronous execution. Control returns to the caller immediately, and the referenced method executes in the context of the first available thread-pool thread.

The signature of the BeginInvoke method includes the same arguments as those specified by the delegate signature, followed by two additional arguments to support asynchronous completion. These additional arguments are

The EndInvoke method allows you to retrieve the return value of a method that was executed asynchronously, but you must first determine when it has finished. Here are the four techniques for determining if an asynchronous method has finished.

The AsyncExecutionExample class in the sample code for this chapter demonstrates use of the asynchronous execution pattern. It uses a delegate named AsyncExampleDelegate to execute a method named LongRunningMethod asynchronously. LongRunningMethod simulates a long-running method using a configurable delay (produced using Thread.Sleep ). Here is the code for AsyncExampleDelegate and LongRunningMethod .

// A delegate that allows you to perform asynchronous execution of // AsyncExecutionExample.LongRunningMethod. public delegate DateTime AsyncExampleDelegate(int delay, string name); // A simulated long running method. public static DateTime LongRunningMethod(int delay, string name) { Console.WriteLine("{0} : {1} example - thread starting.", DateTime.Now.ToString("HH:mm:ss.ffff"), name); // Simulate time consuming processing. Thread.Sleep(delay); Console.WriteLine("{0} : {1} example - thread finishing.", DateTime.Now.ToString("HH:mm:ss.ffff"), name); // Return the method's completion time. return DateTime.Now; }

AsyncExecutionExample contains five methods that demonstrate different approaches for handled asynchronous method completion. A description of these methods and their code is provided here.

The BlockingExample method executes LongRunningMethod asynchronously and continues with a limited set of processing. Once this processing is complete, BlockingExample blocks until LongRunningMethod completes. To block, BlockingExample calls the EndInvoke method of the AsyncExampleDelegate delegate instance. If LongRunningMethod has already finished, EndInvoke returns immediately; otherwise , BlockingExample blocks until LongRunningMethod completes.

public static void BlockingExample() { Console.WriteLine(Environment.NewLine + "*** Running Blocking Example ***"); // Invoke LongRunningMethod asynchronously. Pass null for both the // callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = new AsyncExampleDelegate(LongRunningMethod); IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Blocking", null, null); // Perform other processing until ready to block. for (int count = 0; count < 3; count++) { Console.WriteLine("{0} : Continue processing until ready " + "to block...", DateTime.Now.ToString("HH:mm:ss.ffff")); Thread.Sleep(200); } // Block until the asynchronous method completes and obtain // completion data. Console.WriteLine("{0} : Blocking until method is complete...", DateTime.Now.ToString("HH:mm:ss.ffff")); DateTime completion = longRunningMethod.EndInvoke(asyncResult); // Display completion information Console.WriteLine("{0} : Blocking example complete.", completion.ToString("HH:mm:ss.ffff")); }

The PollingExample method executes LongRunningMethod asynchronously and then enters a polling loop until LongRunningMethod completes. PollingExample tests the IsCompleted property of the IAsyncResult instance returned by BeginInvoke to determine if LongRunningMethod is complete; otherwise, PollingExample calls Thread.Sleep .

public static void PollingExample() { Console.WriteLine(Environment.NewLine + "*** Running Polling Example ***"); // Invoke LongRunningMethod asynchronously. Pass null for both the // callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = new AsyncExampleDelegate(LongRunningMethod); IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Polling", null, null); // Poll the asynchronous method to test for completion. If not // complete sleep for 300ms before polling again. Console.WriteLine("{0} : Poll repeatedly until method is " + "complete...", DateTime.Now.ToString("HH:mm:ss.ffff")); while(!asyncResult.IsCompleted) { Console.WriteLine("{0} : Polling...", DateTime.Now.ToString("HH:mm:ss.ffff")); Thread.Sleep(300); } // Obtain the completion data for the asynchronous method. DateTime completion = longRunningMethod.EndInvoke(asyncResult); // Display completion information Console.WriteLine("{0} : Polling example complete.", completion.ToString("HH:mm:ss.ffff")); }

The WaitingExample method executes LongRunningMethod asynchronously and then waits until LongRunningMethod completes. WaitingExample uses the AsyncWaitHandle property of the IAsyncResult instance returned by BeginInvoke to obtain a WaitHandle and then calls its WaitOne method. Use of a time-out allows WaitingExample to break out of waiting in order to perform other processing or to fail completely if the asynchronous method is taking too long.

public static void WaitingExample() { Console.WriteLine(Environment.NewLine + "*** Running Waiting Example ***"); // Invoke LongRunningMethod asynchronously. Pass null for both the // callback delegate and the asynchronous state object. AsyncExampleDelegate longRunningMethod = new AsyncExampleDelegate(LongRunningMethod); IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Waiting", null, null); // Wait for the asynchronous method to complete. Time out after // 300ms and display status to the console before continuing to // wait. Console.WriteLine("{0} : Waiting until method is complete...", DateTime.Now.ToString("HH:mm:ss.ffff")); while(!asyncResult.AsyncWaitHandle.WaitOne(300, false)) { Console.WriteLine("{0} : Wait timeout...", DateTime.Now.ToString("HH:mm:ss.ffff")); } // Obtain the completion data for the asynchronous method. DateTime completion = longRunningMethod.EndInvoke(asyncResult); // Display completion information Console.WriteLine("{0} : Waiting example complete.", completion.ToString("HH:mm:ss.ffff")); }

The WaitAllExample method executes LongRunningMethod asynchronously multiple times and then uses an array of WaitHandle objects to wait efficiently until all of the methods are complete.

public static void WaitAllExample() { Console.WriteLine(Environment.NewLine + "*** Running WaitAll Example ***"); // An ArrayList to hold the IAsyncResult instances for each of the // asynchronous methods started. ArrayList asyncResults = new ArrayList(3); // Invoke three LongRunningMethods asynchronously. Pass null for // both the callback delegate and the asynchronous state object. // Add the IAsyncResult instance for each method to the ArrayList. AsyncExampleDelegate longRunningMethod = new AsyncExampleDelegate(LongRunningMethod); asyncResults.Add(longRunningMethod.BeginInvoke(3000, "WaitAll 1", null, null)); asyncResults.Add(longRunningMethod.BeginInvoke(2500, "WaitAll 2", null, null)); asyncResults.Add(longRunningMethod.BeginInvoke(1500, "WaitAll 3", null, null)); // Create an array of WaitHandle objects that will be used to wait // for the completion of all of the asynchronous methods. WaitHandle[] waitHandles = new WaitHandle[3]; for (int count = 0; count < 3; count++) { waitHandles[count] = ((IAsyncResult)asyncResults[count]).AsyncWaitHandle; } // Wait for all three asynchronous method to complete. Time out // after 300ms and display status to the console before continuing // to wait. Console.WriteLine("{0} : Waiting until all 3 methods are " + "complete...", DateTime.Now.ToString("HH:mm:ss.ffff")); while(!WaitHandle.WaitAll(waitHandles, 300, false)) { Console.WriteLine("{0} : WaitAll timeout...", DateTime.Now.ToString("HH:mm:ss.ffff")); } // Inspect the completion data for each method and determine the // time at which the final method completed. DateTime completion = DateTime.MinValue; foreach (IAsyncResult result in asyncResults) { DateTime time = longRunningMethod.EndInvoke(result); if ( time > completion) completion = time; } // Display completion information Console.WriteLine("{0} : WaitAll example complete.", completion.ToString("HH:mm:ss.ffff")); }

The CallbackExample method executes LongRunningMethod asynchronously and passes an AsyncCallback delegate instance (that references the CallbackHandler method) to the BeginInvoke method. The referenced CallbackHandler method is called automatically when the asynchronous LongRunningMethod completes, leaving the CallbackExample method completely free to continue processing.

public static void CallbackExample() { Console.WriteLine(Environment.NewLine + "*** Running Callback Example ***"); // Invoke LongRunningMethod asynchronously. Pass an AsyncCallback // delegate instance referencing the CallbackHandler method which // will be called automatically when the asynchronous method // completes. Pass a reference to the AsyncExampleDelegate delegate // instance as asynchronous state; otherwise, the callback method // has no access to the delegate instance in order to call // EndInvoke. AsyncExampleDelegate longRunningMethod = new AsyncExampleDelegate(LongRunningMethod); IAsyncResult asyncResult = longRunningMethod.BeginInvoke(2000, "Callback", new AsyncCallback(CallbackHandler), longRunningMethod); // Continue with other processing. for (int count = 0; count < 15; count++) { Console.WriteLine("{0} : Continue processing...", DateTime.Now.ToString("HH:mm:ss.ffff")); Thread.Sleep(200); } } // A method to handle asynchronous completion using callbacks. public static void CallbackHandler(IAsyncResult result) { // Extract the reference to the AsyncExampleDelegate instance // from the IAsyncResult instance. This allows us to obtain the // completion data. AsyncExampleDelegate longRunningMethod = (AsyncExampleDelegate)result.AsyncState; // Obtain the completion data for the asynchronous method. DateTime completion = longRunningMethod.EndInvoke(result); // Display completion information Console.WriteLine("{0} : Callback example complete.", completion.ToString("HH:mm:ss.ffff")); }

Категории