Subscribe

RSS Feed (xml)

Execute a Method Asynchronously

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
  • A System.AsyncCallback delegate instance that references a method that the runtime will call when the asynchronous method completes. The method is executed in the context of a thread-pool thread. Passing null means that no method is called and you must use another mechanism (discussed later in this recipe) to determine when the asynchronous method is complete.
  • An object reference that the runtime associates with the asynchronous operation. The asynchronous method does not use nor have access to this object but it's available to your code when the method completes, allowing you to associate useful state information with an asynchronous operation. For example, this object allows you to map results against initiated operations in situations where you initiate many asynchronous operations that use a common callback method to perform completion.
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.
  • Blocking Blocking stops the execution of the current thread until the asynchronous method completes execution. In effect, this is much the same as synchronous execution. However, you do have the flexibility to decide exactly when your code enters the blocked state, giving you the opportunity to carry out some additional processing before blocking.
  • Polling Polling involves repeatedly testing the state of an asynchronous method to determine if it's complete. This is a very simple technique and is not particularly efficient from a processing perspective. You should avoid tight loops that consume processor time; it's best to put the polling thread to sleep for a period using Thread.Sleep between completion tests. Because polling involves maintaining a loop, the actions of the waiting thread are limited, but you can easily update some kind of progress indicator.
  • Waiting Waiting uses an object derived from the System.Threading.WaitHandle class to signal when the asynchronous method completes. Waiting is a more efficient version of polling and in addition allows you to wait for multiple asynchronous methods to complete. You can also specify time-out values to allow your waiting thread to fail if the asynchronous method takes too long, or if you want to periodically update a status indicator.
  • Callbacks A callback is a method that the runtime calls when an asynchronous operation completes. The calling code does not have to take any steps to determine when the asynchronous method is complete and is free to continue with other processing. Callbacks provide the greatest flexibility, but also introduce the greatest complexity, especially if you have many asynchronous operations active concurrently that all use the same callback. In such cases, you must use appropriate state objects to match completed methods against those you initiated.
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"));
}

No comments:

Post a Comment

Archives

LocalsAdda.com-Variety In Web World

Fun Mail - Fun in the Mail