Asynchronous Calls

When a client calls a service, usually the client is blocked while the service executes the call, and control returns to the client only when the operation completes its execution and returns. However, there are quite a few cases in which you will want to call operations asynchronously; that is, you'll want control to return immediately to the client while the service executes the operation in the background and then somehow let the client know that the method has completed execution and provide the client with the results of the invocation. Such an execution mode is called asynchronous operation invocation, and the action is known as an asynchronous call. Asynchronous calls allow you to improve client responsiveness and availability.

Requirements for an Asynchronous Mechanism

To make the most of the various options available with WCF asynchronous calls, you should be aware of the generic requirements set for any service-oriented asynchronous call support. These requirements include the following:

  • The same service code should be used for both synchronous and asynchronous invocation. This allows service developers to focus on business logic and cater to both synchronous and asynchronous clients.

  • A corollary of the first requirement is that the client should be the one to decide whether to call a service synchronously or asynchronously. That, in turn, implies that the client will have different code for each case (whether to invoke the call synchronously or asynchronously).

  • The client should be able to issue multiple asynchronous calls and have multiple asynchronous calls in progress, and it should be able to distinguish between multiple methods' completions.

  • Since a service operation's output parameters and return values are not available when control returns to the client, the client should have a way to harvest the results when the operation completes.

  • Similarly, communication errors or errors on the service side should be communicated back to the client side. Any exception thrown during operation execution should be played back to the client later.

  • The implementation of the mechanism should be independent of the binding and transfer technology used. Any binding should support asynchronous calls.

  • The mechanism should not use technology-specific constructs such as .NET exceptions or delegates.

  • The asynchronous calls mechanism should be straightforward and simple to use (this is less of a requirement and more of a design guideline). For example, the mechanism should, as much as possible, hide its implementation details, such as the worker threads used to dispatch the call.

The client has a variety of options for handling operation completion. After it issues an asynchronous call, it can choose to:

  • Perform some work while the call is in progress and then block until completion.

  • Perform some work while the call is in progress and then poll for completion.

  • Receive notification when the method has completed. The notification will be in the form of a callback on a client-provided method. The callback should contain information identifying which operation has just completed and its return values.

  • Perform some work while the call is in progress, wait for a predetermined amount of time, and then stop waiting, even if the operation execution has not yet completed.

  • Wait simultaneously for completion of multiple operations. The client can also choose to wait for all or any of the pending calls to complete.

WCF offers all of these options to clients. The WCF support is strictly a client-side facility, and in fact the service is unaware it is being invoked asynchronously. This means that intrinsically any service supports asynchronous calls, and that you can call the same service both synchronously and asynchronously. In addition, because all of the asynchronous invocation support happens on the client side regardless of the service, you can use any binding for the asynchronous invocation.

Tip

The WCF asynchronous calls support presented in this section is similar but not identical to the delegate-based asynchronous calls support .NET offers for regular CLR types.

Proxy-Based Asynchronous Calls

Because the client decides if the call should be synchronous or asynchronous, you need to create a different proxy for the asynchronous case. In Visual Studio 2008, when adding a service reference, you can click the Advanced button in the Add Service Reference dialog to bring up the settings dialog that lets you tweak the proxy generation. Check the "Generate asynchronous operations" checkbox to generate a proxy that contains asynchronous methods in addition to the synchronous ones. For each operation in the original contract, the asynchronous proxy and contract will contain two additional methods of this form:

[OperationContract(AsyncPattern = true)]
IAsyncResult Begin<Operation>(<in arguments>,
                              AsyncCallback callback,object asyncState);
<returned type> End<Operation>(<out arguments>,IAsyncResult result);

The OperationContract attribute offers the AsyncPattern Boolean property, defined as:

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
   public bool AsyncPattern
   {get;set;}
   //More members
}

The AsyncPattern property defaults to false. AsyncPattern has meaning only on the client side; it is merely a validation flag indicating to the proxy to verify that the method on which this flag is set to true has a Begin<Operation>( )-compatible signature and that the defining contract has a matching method with an End<Operation>( )-compatible signature. These requirements are verified at the proxy load time. AsyncPattern binds the underlying synchronous method with the Begin/End pair and correlates the synchronous execution with the asynchronous one. Briefly, when the client invokes a method of the form Begin<Operation>( ) with AsyncPattern set to true, this tells WCF not to try to directly invoke a method with that name on the service. Instead, WCF should use a thread from the thread pool to synchronously call the underlying method. The synchronous call will block the thread from the thread pool, not the calling client. The client will be blocked for only the slightest moment it takes to dispatch the call request to the thread pool. The reply method of the synchronous invocation is correlated with the End<Operation>( ) method.

Example 8-29 shows a calculator contract and its implementing service, and the generated asynchronous proxy.

Example 8-29. Asynchronous contract and proxy

////////////////////////// Service Side //////////////////////
[ServiceContract]
interface ICalculator
{
   [OperationContract]
   int Add(int number1,int number2);
   //More operations
}
class Calculator : ICalculator
{
   public int Add(int number1,int number2)
   {
      return number1 + number2;
   }
   //Rest of the implementation
}
////////////////////////// Client Side //////////////////////
[ServiceContract]
public interface ICalculator
{
   [OperationContract]
   int Add(int number1,int number2);

   [OperationContract(AsyncPattern = true)]
   IAsyncResult BeginAdd(int number1,int number2,
                         AsyncCallback callback,object asyncState);
   int EndAdd(IAsyncResult result);
   //Rest of the methods
}
partial class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
   public int Add(int number1,int number2)
   {
      return Channel.Add(number1,number2);
   }
   public IAsyncResult BeginAdd(int number1,int number2,
                                AsyncCallback callback,object asyncState)
   {
      return Channel.BeginAdd(number1,number2,callback,asyncState);
   }
   public int EndAdd(IAsyncResult result)
   {
      return Channel.EndAdd(result);
   }
   //Rest of the methods and constructors
}

Asynchronous Invocation

Begin<Operation>( ) accepts the input parameters of the original synchronous operation, which may include data contracts passed by value or by reference (using the ref modifier). The original method's return values and any explicit output parameters (designated using the out and ref modifiers) are part of the End<Operation>( ) method. For example, for this operation definition:

[OperationContract]
string MyMethod(int number1,out int number2,ref int number3);

the corresponding Begin<Operation>( ) and End<Operation>( ) methods look like this:

[ServiceOperation(AsyncPattern = true)]
IAsyncResult BeginMyMethod(int number1,ref int number3,
                           AsyncCallback callback,object asyncState);
string EndMyMethod(out int number2,ref int number3,IAsyncResult result);

Begin<Operation>( ) accepts two additional input parameters that are not present in the original operation signature: callback and asyncState. The callback parameter is a delegate targeting a client-side method-completion notification event. asyncState is an object that conveys whatever state information the party handling the method completion requires. These two parameters are optional: the caller can choose to pass in null instead of either one of them. For example, you could use code like the following to asynchronously invoke the Add( ) method of the Calculator service from Example 8-29 using the asynchronous proxy, if you have no interest in the results or the errors:

CalculatorClient proxy = new CalculatorClient(  );
proxy.BeginAdd(2,3,null,null); //Dispatched asynchronously
proxy.Close(  );

As long as the client has the definition of the asynchronous contract, you can also invoke the operation asynchronously using a channel factory:

ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(  );
ICalculator proxy = factory.CreateChannel(  );
proxy.BeginAdd(2,3,null,null);
ICommunicationObject channel = proxy as ICommunicationObject;
channel.Close(  );

The problem with such an invocation is that the client has no way of getting its results.

The IAsyncResult interface

Every Begin<Operation>( ) method returns an object implementing the IAsyncResult interface, defined in the System.Runtime.Remoting.Messaging namespace as:

public interface IAsyncResult
{
   object AsyncState
   {get;}
   WaitHandle AsyncWaitHandle
   {get;}
   bool CompletedSynchronously
   {get;}
   bool IsCompleted
   {get;}
}

The returned IAsyncResult implementation uniquely identifies the method that was invoked using Begin<Operation>( ). You can pass the IAsyncResult-implementation object to End<Operation>( ) to identify the specific asynchronous method execution from which you wish to retrieve the results. End<Operation>( ) will block its caller until the operation it's waiting for (identified by the IAsyncResult-implementation object passed in) completes and it can return the results or errors. If the method is already complete by the time End<Operation>( ) is called, End<Operation>( ) will not block the caller and will just return the results. Example 8-30 shows the entire sequence.

Example 8-30. Simple asynchronous execution sequence

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result1 = proxy.BeginAdd(2,3,null,null);
IAsyncResult result2 = proxy.BeginAdd(4,5,null,null);

/* Do some work */

int sum;

sum = proxy.EndAdd(result1); //This may block
Debug.Assert(sum == 5);
sum = proxy.EndAdd(result2); //This may block
Debug.Assert(sum == 9);

proxy.Close(  );

As simple as Example 8-30 is, it does demonstrate a few key points. The first point is that the same proxy instance can invoke multiple asynchronous calls. The caller can distinguish among the different pending calls using each unique IAsyncResult-implementation object returned from Begin<Operation>( ). In fact, when the caller makes asynchronous calls, as in Example 8-30, it must save the IAsyncResult-implementation objects. In addition, the caller should make no assumptions about the order in which the pending calls will complete. It is quite possible that the second call will complete before the first one.

Although it isn't evident in Example 8-30, there are two important programming points regarding asynchronous calls:

  • End<Operation>( ) can be called only once for each asynchronous operation. Trying to call it more than once results in an InvalidOperationException.

  • You can pass the IAsyncResult-implementation object to End<Operation>( ) only on the same proxy object used to dispatch the call. Passing the IAsyncResult-implementation object to a different proxy instance results in an AsyncCallbackException. This is because only the original proxy keeps track of the asynchronous operations it has invoked.

Asynchronous calls and transport sessions

If the proxy is not using a transport session, the client can close the proxy immediately after the call to Begin<Operation>( ) and still be able to call End<Operation>( ) later:

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result = proxy.BeginAdd(2,3,null,null);
proxy.Close(  );

/*Do some work */

//Sometime later:
int sum = proxy.EndAdd(result);
Debug.Assert(sum == 5);

Polling or Waiting for Completion

When a client calls End<Operation>( ), the client is blocked until the asynchronous method returns. This may be fine if the client has a finite amount of work to do while the call is in progress, and if after completing that work the client cannot continue its execution without the returned value or the output parameters of the operation. However, what if the client only wants to check that the operation has completed? What if the client wants to wait for completion for a fixed timeout and then, if the operation has not completed, do some additional finite processing and wait again? WCF supports these alternative programming models to calling End<Operation>( ).

The IAsyncResult interface object returned from Begin<Operation>( ) has the AsyncWaitHandle property, of type WaitHandle:

public abstract class WaitHandle : ...
{
   public static bool WaitAll(WaitHandle[] waitHandles);
   public static int WaitAny(WaitHandle[] waitHandles);
   public virtual void Close(  );
   public virtual bool WaitOne(  );
   //More memebrs
}

The WaitOne( ) method of WaitHandle returns only when the handle is signaled. Example 8-31 demonstrates using WaitOne( ).

Example 8-31. Using IAsyncResult.AsyncWaitHandle to block until completion

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result = proxy.BeginAdd(2,3,null,null);

/* Do some work */

result.AsyncWaitHandle.WaitOne(  ); //This may block
int sum = proxy.EndAdd(result); //This will not block
Debug.Assert(sum == 5);

proxy.Close(  );

Logically, Example 8-31 is identical to Example 8-30, which called only End<Operation>( ). If the operation is still executing when WaitOne( ) is called, WaitOne( ) will block. But if by the time WaitOne( ) is called the method execution is complete, WaitOne( ) will not block, and the client will proceed to call End<Operation>( ) for the returned value. The important difference between Example 8-31 and Example 8-30 is that the call to End<Operation>( ) in Example 8-31 is guaranteed not to block its caller.

Example 8-32 demonstrates a more practical way of using WaitOne( ), by specifying a timeout (10 milliseconds in this example). When you specify a timeout, WaitOne( ) returns when the method execution is completed or when the timeout has elapsed, whichever condition is met first.

Example 8-32. Using WaitOne( ) to specify wait timeout

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result = proxy.BeginAdd(2,3,null,null);
while(result.IsCompleted == false)
{
   result.AsyncWaitHandle.WaitOne(10,false); //This may block
   /* Do some optional work */
}
int sum = proxy.EndAdd(result); //This will not block

Example 8-32 uses another handy property of IAsyncResult, called IsCompleted. IsCompleted lets you check the status of the call without waiting or blocking. You can even use IsCompleted in a strict polling mode:

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result = proxy.BeginAdd(2,3,null,null);

//Sometime later:
if(result.IsCompleted)
{
   int sum = proxy.EndAdd(result); //This will not block
   Debug.Assert(sum == 5);
}
else
{
  //Do some optional work
}
proxy.Close(  );

The AsyncWaitHandle property really shines when you use it to manage multiple concurrent asynchronous methods in progress. You can use WaitHandle's static WaitAll( ) method to wait for completion of multiple asynchronous methods, as shown in Example 8-33.

Example 8-33. Waiting for completion of multiple methods

CalculatorClient proxy = new CalculatorClient(  );
IAsyncResult result1 = proxy.BeginAdd(2,3,null,null);
IAsyncResult result2 = proxy.BeginAdd(4,5,null,null);

WaitHandle[] handleArray = {result1.AsyncWaitHandle,result2.AsyncWaitHandle};

WaitHandle.WaitAll(handleArray);

int sum;
//These calls to EndAdd(  ) will not block

sum = proxy.EndAdd(result1);
Debug.Assert(sum == 5);

sum = proxy.EndAdd(result2);
Debug.Assert(sum == 9);

proxy.Close(  );

To use WaitAll( ), you need to construct an array of handles. Note that you still need to call End<Operation>( ) to access the returned values. Instead of waiting for all of the methods to return, you can choose to wait for any of them to return, using the WaitAny( ) static method of the WaitHandle class. Like WaitOne( ), both WaitAll( ) and WaitAny( ) have overloaded versions that let you specify a timeout to wait instead of waiting indefinitely.

Completion Callbacks

Instead of blocking, waiting, and polling for asynchronous call completion, WCF offers another programming model altogetherā€”completion callbacks. With this model, the client provides WCF with a method and requests that WCF call that method back when the asynchronous method completes. The client can have the same callback method handle completion of multiple asynchronous calls. When each asynchronous method's execution is complete, instead of quietly returning to the pool, the worker thread calls the completion callback. To designate a completion callback method, the client needs to provide Begin<Operation>( ) with a delegate of the type AsyncCallback, defined as:

public delegate void AsyncCallback(IAsyncResult result);

That delegate is provided as the penultimate parameter to Begin<Operation>( ).

Example 8-34 demonstrates asynchronous call management using a completion callback.

Example 8-34. Managing asynchronous call with a completion callback

class MyClient : IDisposable
{
   CalculatorClient m_Proxy = new CalculatorClient(  );

   public void CallAsync(  )
   {
      m_Proxy.BeginAdd(2,3,OnCompletion,null);
   }
   void OnCompletion(IAsyncResult result)
   {
      int sum = m_Proxy.EndAdd(result);
      Debug.Assert(sum == 5);
   }
   public void Dispose(  )
   {
      m_Proxy.Close(  );
   }
}

Unlike the programming models described so far, when you use a completion callback method, there's no need to save the IAsyncResult-implementation object returned from Begin<Operation>( ). This is because when WCF calls the completion callback, WCF provides the IAsyncResult-implementation object as a parameter. Because WCF provides a unique IAsyncResult-implementation object for each asynchronous method, you can channel multiple asynchronous method completions to the same callback method:

m_Proxy.BeginAdd(2,3,OnCompletion,null);
m_Proxy.BeginAdd(4,5,OnCompletion,null);

Instead of using a class method as a completion callback, you can just as easily use a local anonymous method or a lambda expression:

CalculatorClient proxy = new CalculatorClient(  );
int sum;
AsyncCallback completion = (result)=>
                           {
                              sum = proxy.EndAdd(result);
                              Debug.Assert(sum == 5);
                              proxy.Close(  );
                           };
proxy.BeginAdd(2,3,completion,null);

Note that the anonymous method assigns to an outer variable (sum) to provide the result of the Add( ) operation.

Callback completion methods are by far the preferred model in any event-driven application. An event-driven application has methods that trigger events (or requests) and methods that handle those events and fire their own events as a result. Writing an application as event-driven makes it easier to manage multiple threads, events, and callbacks and allows for scalability, responsiveness, and performance.

The last thing you want in an event-driven application is to block, since then your application does not process events. Callback completion methods allow you to treat the completion of the asynchronous operation as yet another event in your system. The other options (waiting, blocking, and polling) are available for applications that are strict, predictable, and deterministic in their execution flow. I recommend that you use completion callback methods whenever possible.

Completion callbacks and thread safety

Because the callback method is executed on a thread from the thread pool, you must provide for thread safety in the callback method and in the object that provides it. This means that you must use synchronization objects and locks to access the member variables of the client, even outer variables to anonymous completion methods. You need to provide for synchronization between client-side threads and the worker thread from the pool, and potentially synchronizing between multiple worker threads all calling concurrently into the completion callback method to handle their respective asynchronous call completion. Therefore, you need to make sure the completion callback method is reentrant and thread-safe.

Passing state information

The last parameter to Begin<Operation>( ) is asyncState. The asyncState object, known as a state object, is provided as an optional container for whatever need you deem fit. The party handling the method completion can access such a container object via the AsyncState property of IAsyncResult. Although you can certainly use state objects with any of the other asynchronous call programming models (blocking, waiting, or polling), they are most useful in conjunction with completion callbacks. The reason is simple: when you are using a completion callback, the container object offers the only way to pass in additional parameters to the callback method, whose signature is predetermined.

Example 8-35 demonstrates how you might use a state object to pass an integer value as an additional parameter to the completion callback method. Note that the callback must downcast the AsyncState property to the actual type.

Example 8-35. Passing an additional parameter using a state object

class MyClient : IDisposable
{
   CalculatorClient m_Proxy = new CalculatorClient(  );

   public void CallAsync(  )
   {
      int asyncState = 4; //int, for example
      m_Proxy.BeginAdd(2,3,OnCompletion,asyncState);
   }
   void OnCompletion(IAsyncResult result)
   {
      int asyncState = (int)result.AsyncState;
      Debug.Assert(asyncState == 4);

      int sum = m_Proxy.EndAdd(result);
   }
   public void Dispose(  )
   {
      m_Proxy.Close(  );
   }
}

A common use for the state object is to pass the proxy used for Begin<Operation>( ) instead of saving it as a member variable:

class MyClient
{
   public void CallAsync(  )
   {
      CalculatorClient proxy = new CalculatorClient(  );
      proxy.BeginAdd(2,3,OnCompletion,proxy);
   }
   void OnCompletion(IAsyncResult result)
   {
      CalculatorClient proxy = result.AsyncState as CalculatorClient;
      Debug.Assert(proxy != null);

      int sum = proxy.EndAdd(result);
      Debug.Assert(sum == 5);

      proxy.Close(  );
   }
}

Completion callback synchronization context

The completion callback, by default, is called on a thread from the thread pool. This presents a serious problem if the callback is to access some resources that have an affinity to a particular thread or threads and are required to run in a particular synchronization context. The classic example is a Windows Forms application that dispatches a lengthy service call asynchronously (to avoid blocking the UI), and then wishes to update the UI with the result of the invocation. Using the raw Begin<Operation>( ) is disallowed, since only the UI thread is allowed to update the UI. You must marshal the call from the completion callback to the correct synchronization context, using any of the techniques described previously (such as safe controls). Example 8-36 demonstrates such a completion callback that interacts directly with its containing form, ensuring that the UI update will be in the UI synchronization context.

Example 8-36. Relying on completion callback synchronization context

partial class CalculatorForm : Form
{
   CalculatorClient m_Proxy;
   SynchronizationContext m_SynchronizationContext;

   public CalculatorForm(  )
   {
      InitializeComponent(  );
      m_Proxy = new CalculatorClient(  );
      m_SynchronizationContext = SynchronizationContext.Current;
   }
   public void CallAsync(object sender,EventArgs args)
   {
      m_Proxy.BeginAdd(2,3,OnCompletion,null);
   }
   void OnCompletion(IAsyncResult result)
   {
      SendOrPostCallback callback = delegate
                                    {
                                       Text = "Sum = " + m_Proxy.EndAdd(result);
                                    };
      m_SynchronizationContext.Send(callback,null);
   }
   public void OnClose(object sender,EventArgs args)
   {
      m_Proxy.Close(  );
   }
}

To better handle this situation, the ClientBase<T> base class in .NET 3.5 is extended with a protected InvokeAsync( ) method that picks up the synchronization context of the client and uses it to invoke the completion callback, as shown in Example 8-37.

Example 8-37. Async callback management in ClientBase<T>

public abstract class ClientBase<T> : ...
{
   protected delegate IAsyncResult BeginOperationDelegate(object[] inValues,
                                         AsyncCallback asyncCallback,object state);

   protected delegate object[] EndOperationDelegate(IAsyncResult result);

   //Picks up sync context and used for completion callback
   protected void InvokeAsync(BeginOperationDelegate beginOpDelegate,
                              object[] inValues,
                              EndOperationDelegate endOpDelegate,
                              SendOrPostCallback opCompletedCallback,
                              object userState);
   //More members
}

ClientBase<T> also provides an event arguments helper class and two dedicated delegates used to invoke and end the asynchronous call. The generated proxy class that derives from ClientBase<T> makes use of the base functionality. The proxy will have a public event called <Operation>Completed that uses a strongly typed event argument class specific to the results of the asynchronous method, and two methods called <Operation>Async that are used to dispatch the call asynchronously:

partial class AddCompletedEventArgs : AsyncCompletedEventArgs
{
   public int Result
   {get;}
}

class CalculatorClient : ClientBase<ICalculator>,ICalculator
{
   public event EventHandler<AddCompletedEventArgs> AddCompleted;

   public void AddAsync(int number1,int number2,object userState);
   public void AddAsync(int number1,int number2);

   //Rest of the proxy
}

The client can subscribe an event handler to the <Operation>Completed event to have that handler called upon completion. The big difference with using <Operation>Async as opposed to Begin<Operation> is that the <Operation>Async methods will pick up the synchronization context of the client and will fire the <Operation>Completed event on that synchronization context, as shown in Example 8-38.

Example 8-38. Synchronization-context-friendly asynchronous call invocation

partial class CalculatorForm : Form
{
   CalculatorClient m_Proxy;

   public CalculatorForm(  )
   {
      InitializeComponent(  );

      m_Proxy = new CalculatorClient(  );
      m_Proxy.AddCompleted += OnAddCompleted;
   }
   void CallAsync(object sender,EventArgs args)
   {
      m_Proxy.AddAsync(2,3); //Sync context picked up here
   }
   //Called on the UI thread
   void OnAddCompleted(object sender,AddCompletedEventArgs args)
   {
      Text = "Sum = " + args.Result;
   }
}

One-Way Asynchronous Operations

There is little sense in trying to invoke a one-way operation asynchronously, because while one of the main features of asynchronous calls is their ability to retrieve and correlate a reply message, no such message is available with a one-way call. If you do invoke a one-way operation asynchronously, End<Operation>( ) will return as soon as the worker thread has finished dispatching the call. Aside from communication errors, End<Operation>( ) will not encounter any exceptions. If a completion callback is provided for an asynchronous invocation of a one-way operation, the callback is called immediately after the worker thread used in Begin<Operation>( ) dispatches the call. The only justification for invoking a one-way operation asynchronously is to avoid the potential blocking of the one-way call, in which case you should pass a null for the state object and the completion callback, as shown in Example 8-39.

Example 8-39. Invoking a one-way operation asynchronously

[ServiceContract]
interface IMyContract
{
   [OperationContract(IsOneWay = true)]
   void MyMethod(string text);

   [OperationContract(IsOneWay = true,AsyncPattern = true)]
   IAsyncResult BeginMyMethod(string text,
                              AsyncCallback callback,object asyncState);
   void EndMyMethod(IAsyncResult result);
}
MyContractClient proxy = MyContractClient(  );
proxy.BeginMyMethod("Async one way",null,null);

//Sometime later:
proxy.Close(  );

The problem with Example 8-39 is the potential race condition of closing the proxy. It is possible to push the asynchronous call with Begin<Operation>( ) and then close the proxy before the worker thread used has had a chance to invoke the call. If you want to close the proxy immediately after asynchronously invoking the one-way call, you need to provide a completion method for closing the proxy:

MyContractClient proxy = MyContractClient(  );

AsyncCallback completion = (result)=>
                           {
                              proxy.Close(  );
                           };
proxy.BeginMyMethod("Async one way",completion,null);

Asynchronous Error Handling

Output parameters and return values are not the only elements unavailable at the time an asynchronous call is dispatched: exceptions are missing as well. After calling Begin<Operation>( ), control returns to the client, but it may be some time before the asynchronous method encounters an error and throws an exception, and some time after that before the client actually calls End<Operation>( ). WCF must therefore provide some way for the client to know that an exception was thrown and allow the client to handle it. When the asynchronous method throws an exception, the proxy catches it, and when the client calls End<Operation>( ) the proxy rethrows that exception object, letting the client handle the exception. If a completion callback is provided, WCF calls that method immediately after the exception is received. The exact exception thrown is compliant with the fault contract and the exception type, as explained in Chapter 6.

Tip

If fault contracts are defined on the service operation contract, the FaultContract attribute should be applied only on the synchronous operations.

Asynchronous calls and timeouts

Since the asynchronous invocation mechanism is nothing but a convenient programming model on top of the actual synchronous operation, the underlying synchronous call can still time out. This will result in a TimeoutException when the client calls End<Operation>( ). It is therefore wrong to equate asynchronous calls with lengthy operations. By default, asynchronous calls are still relatively short (under a minute), but unlike synchronous calls, they are non-blocking. For lengthy asynchronous calls you will need to provide an adequately long send timeout.

Cleaning up after End<Operation>( )

When the client calls Begin<Operation>( ), the returned IAsyncResult will have a reference to a single WaitHandle object, accessible via the AsyncWaitHandle property. Calling End<Operation>( ) on that object will not close the handle. Instead, that handle will be closed when the implementing object is garbage-collected. As with any other case of using an unmanaged resource, you have to be mindful about your application-deterministic finalization needs. It is possible (in theory, at least) for the application to dispatch asynchronous calls faster than .NET can collect the handles, resulting in a resource leak. To compensate, you can explicitly close that handle after calling End<Operation>( ). For example, using the same definitions as those in Example 8-34:

void OnCompletion(IAsyncResult result)
{
   int sum = m_Proxy.EndAdd(result);
   Debug.Assert(sum == 5);
   result.AsyncWaitHandle.Close(  );
}

Asynchronous Calls and Transactions

Transactions do not mix well with asynchronous calls, for a few reasons. First, well-designed transactions are of short duration, yet the main motivation for using asynchronous calls is because of the latency of the operations. Second, the client's ambient transaction will not by default flow to the service, because the asynchronous operation is invoked on a worker thread, not the client's thread. While it is possible to develop a proprietary mechanism that uses cloned transactions, this is esoteric at best and should be avoided. Finally, when a transaction completes, it should have no leftover activities to do in the background that could commit or abort independently of the transaction; however, this will be the result of spawning an asynchronous operation call from within a transaction. In short, do not mix transactions with asynchronous calls.

Synchronous Versus Asynchronous Calls

Although it is technically possible to call the same service synchronously and asynchronously, the likelihood that a service will be accessed both ways is low.

The reason is that using a service asynchronously necessitates drastic changes to the workflow of the client, and consequently the client cannot simply use the same execution sequence logic as with synchronous access. Consider, for example, an online store application. Suppose the client (a server-side object executing a customer request) accesses a Store service, where it places the customer's order details. The Store service uses three well-factored helper services to process the order: Order, Shipment, and Billing. In a synchronous scenario, the Store service first calls the Order service to place the order. Only if the Order service succeeds in processing the order (i.e., if the item is available in the inventory) does the Store service then call the Shipment service, and only if the Shipment service succeeds does the Store service access the Billing service to bill the customer. This sequence is shown in Figure 8-4.

Synchronous processing of an order

Figure 8-4. Synchronous processing of an order

The downside to the workflow shown in Figure 8-4 is that the store must process orders synchronously and serially. On the surface, it might seem that if the Store service invoked its helper objects asynchronously, it would increase throughput, because it could process incoming orders as fast as the client submitted them. The problem in doing so is that it is possible for the calls to the Order, Shipment, and Billing services to fail independently, and if they do, all hell will break loose. For example, the Order service might discover that there were no items in the inventory matching the customer request, while the Shipment service tried to ship the nonexisting item and the Billing service had already billed the customer for it.

Using asynchronous calls on a set of interacting services requires that you change your code and your workflow. As illustrated in Figure 8-5, to call the helper services asynchronously, you need to string them together. The Store service should call only the Order service, which in turn should call the Shipment service only if the order processing was successful, to avoid the potential inconsistencies just mentioned. Similarly, only in the case of successful shipment should the Shipment service asynchronously call the Billing service.

Revised workflow for asynchronous processing of an order

Figure 8-5. Revised workflow for asynchronous processing of an order

In general, if you have more than one service in your asynchronous workflow, you should have each service invoke the next one in the logical execution sequence. Needless to say, such a programming model introduces tight coupling between services (they have to know about each other) and changes to their interfaces (you have to pass in additional parameters, which are required for the desired invocation of services downstream).

The conclusion is that using asynchronous instead of synchronous invocation introduces major changes to the service interfaces and the client workflow. Asynchronous invocation on a service that was built for synchronous execution works only in isolated cases. When dealing with a set of interacting services, it is better to simply spin off a worker thread to call them and use the worker thread to provide asynchronous execution. This will preserve the service interfaces and the original client execution sequence.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.113.55