Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Asynchronous Calls in .NET : Page 3

Invoking methods asynchronously involves a substantial amount of infrastructure: keeping track of methods in progress, multithreading, and error handling. Fortunately, .NET provides a standard method for asynchronous invocation that's consistent and available to every component and client.


advertisement

Asynchronous Calls Programming Models
To support asynchronous invocation, multiple threads are required. However, it would be a waste of system resources and a performance penalty if .NET spun off a new thread for every asynchronous method invocation. A better approach is to use the .NET thread pool. The .NET way of supporting asynchronous calls is to hide this interaction completely. There are quite a few programming models available when dealing with asynchronous calls, all of which comply with the general requirements set at the beginning of this article. In general, use BeginInvoke() to initiate an asynchronous method invocation. The calling client is blocked only for the briefest moment, the time it takes to queue up a request for a thread from the thread pool to execute the method, and then control returns to the client. You use EndInvoke() to manage method completion, specifically, retrieving out-going parameters and return values as well as error handling.

Using BeginInvoke() and EndInvoke()
The compiler-generated BeginInvoke() and EndInvoke() methods take this general form:

   public virtual IAsyncResult BeginInvoke(
      <input and input/output parameters>,
      AsyncCallback callback,
      object asyncState);
   public virtual <return value> EndInvoke(
      <output and input/output parameters>,
      IAsyncResult asyncResult);
BeginInvoke() accepts the parameters of the original signature that the delegate defines. These parameters include: value types, passed by reference (using out or ref modifiers), and reference types. The original method's return values and any explicit outgoing parameters are part of the EndInvoke() method. For example, for this delegate definition:

   public delegate string MyDelegate(int num1,
      out int num2, ref int num3,
      object obj);
The corresponding BeginInvoke() and EndInvoke() methods would look like this:



   public virtual IAsyncResult BeginInvoke(
                              int num1,
                              out int num2,
                              ref int num3,
                              object obj,
                              AsyncCallback callback,
                              object asyncState);
   public virtual string EndInvoke(
                          out int num2,
                          ref int num3,
                          IAsyncResult asyncResult);
BeginInvoke() accepts two additional parameters that were not present in the original delegate signature: AsyncCallback callback and object asyncState. The callback parameter is actually a delegate object representing a reference to a callback method used to receive the method completed notification event. asyncState is a generic object used to pass in whatever state information is needed by the party handling the method completion. These two parameters are optional in that the caller can choose to pass in null instead of either one of them. For example, to asynchronously invoke the Add() method of the Calculator class, if you have no interest in the result, and no interest in a callback method or state information, you would write:

   Calculator calc = new Calculator();
   BinaryOperation oppDel;
   oppDel = new BinaryOperation(calc.Add);
   oppDel.BeginInvoke(2,3,null,null);
The object itself is unaware that the client is using a delegate to asynchronously invoke the method. The same object code handles both the synchronous and the asynchronous invocation cases. As a result, every .NET class supports asynchronous invocation.

The .NET mechanism for asynchronous calls is a mainstream facility used consistently and pervasively across the .NET application frameworks and base classes
Because you can use delegates on both instance methods and static methods, clients can use BeginInvoke() to asynchronously call static methods as well. The remaining question is, how would you get the results of the method?

The IAsyncResult Interface

Every BeginInvoke() method returns an object implementing the IAsyncResult interface, defined as:

   public interface IAsyncResult 
   { 
      object AsyncState{get;}
      WaitHandle AsyncWaitHandle{get;}
      bool CompletedSynchronously{get;}
      bool IsCompleted{get;}
   }
You will see a few uses for the properties of IAsyncResult later on. For now, it is sufficient to know that the returned IAsyncResult object uniquely identifies the method that was invoked using BeginInvoke(). You can pass the IAsyncResult object to EndInvoke() to identify the specific asynchronous method execution from which you wish to retrieve the results. Listing 1 shows the entire sequence.

As simple as Listing 1 is, it does demonstrate a few key points. The most important of them is that because you primarily use EndInvoke() to retrieve any outgoing parameters as well as the method return value, EndInvoke() will block its caller until the method it waits for (identified by the IAsyncResult object passed in) returns. The second point is that you can use the same delegate object (with exactly one target method) to invoke multiple asynchronous calls on the target method. The caller can distinguish between the different pending calls using each unique IAsyncResult object returned from BeginInvoke(). In fact, when the caller invokes asynchronous calls, as in Listing 1, the caller must save the IAsyncResult objects. In addition, the caller should make no assumption about the order in which the pending calls complete. Remember—the asynchronous calls are carried out on threads from the thread pool, and because of thread context-switches (as well as internal pool management), it is possible the second call will complete before the first one.

There are other uses for the IAsyncResult object besides passing it to EndInvoke(). You can use it to get the state object parameter of BeginInvoke(). You can wait for the method completion. Or, you can get the original delegate used to invoke the call. You will read how to use these later on.

Although it is not evident in Listing 1, there are three important programming points you must always remember when you use delegate-based asynchronous calls:

You can only call EndInvoke() once for each asynchronous operation. Trying to call EndInvoke() more than once will result in an exception of type InvalidOperationException.

Although the compiler-generated delegate class can manage multiple targets, when you use asynchronous calls, the delegate can only have exactly one target method in its internal list. If you call BeginInvoke() when the delegate's list has more than one target, you'll get an ArgumentException stating that the delegate must have only one target.

You can only pass the IAsyncResult object to EndInvoke() on the same delegate object used to dispatch the call. Passing the IAsyncResult object to a different delegate would result in an exception of type InvalidOperationException stating, "The IAsyncResult object provided does not match this delegate." Listing 2 demonstrates this point. It results in the exception, even though the other delegate targets the same method.

The AsyncResult Class

Often, one client will initiate the asynchronous call and another client will call EndInvoke(). Even if only one client is involved, it is likely that it will call BeginInvoke() in one code section (or method) and EndInvoke() in another. It is bad enough you have to either save the IAsyncResult object or pass it around between clients. It would be even worse if you have to do the same for the delegate object used to invoke the asynchronous call, just because you need that delegate to call EndInvoke() on. As it turns out, there is an easier solution. The IAsyncResult object itself carries with it the delegate that created it. When BeginInvoke() returns the IAsyncResult reference, it is actually an instance of a class called AsyncResult, defined as:

   public class AsyncResult : IAsyncResult, IMessageSink
   {
     //IAsyncResult implementation 
     public object AsyncState {virtual get;}
     public WaitHandle AsyncWaitHandle{virtual get;} 
     public bool CompletedSynchronously{virtual get;}
     public bool IsCompleted {virtual get;}
     //Other properties
     public bool EndInvokeCalled{get; set;}
     public object AsyncDelegate{virtual get;}
     /* IMessageSink implementation  */
   }
You'll find AsyncResult in the System.Runtime.Remoting.Messaging namespace. AsyncResult has a property called AsyncDelegate, which is a reference to the original delegate used to dispatch the call. Listing 3 shows how to use the AsyncDelegate property to call EndInvoke() on the original delegate.

Note that because the AsyncDelegate property is of type object, you need to downcast it to the actual delegate type (see BinaryOperation in Listing 3).

Listing 3 demonstrates using another useful property of AsyncResult—the Boolean EndInvokeCalled property. You can use it to verify that EndInvoke() was not called yet:

   Debug.Assert(asyncResult.EndInvokeCalled == false);


Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap
Thanks for your registration, follow us on our social networks to keep up-to-date