Asynchronous Calls



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 want to call operations asynchronously; that is, you 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 provides 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.

One-way operations are inadequate for asynchronous calls. First, one-way calls are not guaranteed to be asynchronous at all. If the service's incoming calls queue is filled to capacity, WCF will block the caller of a one-way call until it can place the call in the queue. In addition, there is no easy way to notify the client regarding the results or errors of the call when a one-way operation completes. While you could hand-craft a custom mechanism that passes method IDs to every one-way call and then uses a callback to report completion, results, and errors back to the client, such a solution would be cumbersome and proprietary. It would require the service to always catch all exceptions, while communication errors may not reach the client at all. It also mandates the use of a duplex binding, and you will not be able to call the service operation both synchronously and asynchronously.


Requirements for an Asynchronous Mechanism

To make the most of the various options available with WCF asynchronous calls, it is best to first list generic requirements set for any service-oriented asynchronous calls support. These 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. The client should be able to distinguish between multiple methods completions.

  • When a service operation has output parameters or return values, these parameters are not available when control returns to the client. The client should have a way to harvest these results when the operation completes.

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

  • 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 last item is less of a requirement and more of a design guideline: the asynchronous calls mechanism should be straightforward and simple to use. 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. The client issues an asynchronous call and then 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, then wait for only a predetermined amount of time, and stop waiting, even if the operation execution has not completed yet.

  • 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.

The WCF asynchronous calls support presented in this section is similar but not identical to the delegate-based asynchronous calls support offered by .NET 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. Using the /async switch of SvcUtil, you can 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,
                   Action = "<original action name>",
                   ReplyAction = "<original response name">)]
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 copy of the contract. You can only set AsyncPattern to true on a method with a Begin<Operation>( )-compatible signature, and the defining contract must also have a matching method with an End<Operation>( )-compatible signature. These requirements are verified at the proxy load time. What AsyncPattern does is bind 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, it tells WCF not to try to directly invoke a method by that name on the service. Instead, it will use a thread from the thread pool to synchronously call the underlying method (identified by the Action name). The synchronous call will block the thread from the thread pool, not the calling client. The client will only be blocked for 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.

Figure shows a calculator contract and implementing service, and the generated proxy class when the /async switch is used.

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,
                      Action = ".../ICalculator/Add",
                      ReplyAction = ".../ICalculator/AddResponse")]
   IAsyncResult BeginAdd(int number1,int number2,AsyncCallback callback,
                                                                object asyncState);
   int EndAdd(IAsyncResult result);

   //Rest of the methods
}
public 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
}

Note that the BeginAdd( ) operation on the contract still has the original action and reply names, and in fact, you can just omit them:

[OperationContract(AsyncPattern = true)]
IAsyncResult BeginAdd(int number1,int number2,AsyncCallback callback,
                                                                object asyncState);

Asynchronous Invocation

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

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

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

[ServiceOperation(...)]
IAsyncResult BeginMyMethod(int number1,ref int number3,
                           AsyncCallback callback,object asyncState);
string EndMyMethod(out int number2,ref int number3,IAsyncResult asyncResult);

Begin<Operation>( ) accepts two additional input parameters, not present in the original operation signature: callback and asyncState. The callback parameter is a delegate targeting a client-side method completed notification event. asyncState is an object that conveys whatever state information is needed by the party handling the method completion. These two parameters are optional: 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 service from Figure 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 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 object uniquely identifies the method that was invoked using Begin<Operation>( ). You can pass the IAsyncResult 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 waits for (identified by the IAsyncResult object passed in) completes and it can rerun the results or the 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. Figure shows the entire sequence.

Simple asynchronous execution sequence

CalculatorClient proxy = new CalculatorClient( );
IAsyncResult asyncResult1 = proxy.BeginAdd(2,3,null,null);
IAsyncResult asyncResult2 = proxy.BeginAdd(4,5,null,null);
proxy.Close( );

/* Do some work */

int sum;

sum = proxy.EndAdd(asyncResult1);//This may block
Debug.Assert(sum == 5);

sum = proxy.EndAdd(asyncResult2);//This may block
Debug.Assert(sum == 9);

As simple as Figure 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 object returned from Begin<Operation>( ). In fact, when the caller makes asynchronous calls, as in Figure, the caller must save the IAsyncResult objects. In addition, the caller should make no assumptions about the order in which the pending calls complete. It is quite possible the second call will complete before the first one. Finally, if you have no more use for the proxy, you can close it immediately after dispatching the asynchronous calls, and still be able to call End<Operation>( ).

Although it isn't evident in Figure, 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 object to End<Operation>( ) only on the same proxy object used to dispatch the call. Passing the IAsyncResult object to a different proxy instance results in an AsyncCallbackException.

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, once that work is done, the client cannot continue its execution without the returned value or the output parameters of the operation, or even just the knowledge that the operation has completed. However, what if the client only wants to check if the operation execution has completed? What if the client wants to wait for completion for a fixed timeout, do some additional finite processing, and then 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. Figure demonstrates using WaitOne( ).

UsingIasyncResult.AsyncWaitHandle to block until completion

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

/* Do some work */

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

Logically, Figure is identical to Figure, which called only End<Operation>( ). If the operation is still executing, WaitOne( ) will block. If by the time WaitOne( ) is called, the method execution is complete, WaitOne( ) will not block, and the client proceeds to call End<Operation>( ) for the returned value. The important difference between Examples 8-28 and 8-27 is that the call to End<Operation>( ) in Figure is guaranteed not to block its caller.

Figure demonstrates a more practical way of using WaitOne( ), by specifying the 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.

Using WaitOne( ) to specify wait timeout

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

while(asyncResult.IsCompleted == false)
{
   asyncResult.AsyncWaitHandle.WaitOne(10,false); //This may block
   /* Do some work */
}
int sum = proxy.EndAdd(asyncResult); //This will not block

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

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

//Sometime later:
if(asyncResult.IsCompleted)
{
   int sum = proxy.EndAdd(asyncResult); //This will not block
   Debug.Assert(sum == 5);
}
else
{
  //Do something meanwhile
}

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 Figure.

Waiting for completion of multiple methods

CalculatorClient proxy = new CalculatorClient( );
IAsyncResult asyncResult1 = proxy.BeginAdd(2,3,null,null);
IAsyncResult asyncResult2 = proxy.BeginAdd(4,5,null,null);
proxy.Close( );

WaitHandle[] handleArray = {asyncResult1.AsyncWaitHandle,
                            asyncResult2.AsyncWaitHandle};

WaitHandle.WaitAll(handleArray);

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

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

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

To use WaitAll( ), you need to construct an array of handles. Note that you still need to call End<Operation>( ) to access 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. Much like WaitOne( ), both WaitAll( ) and WaitAny( ) have a few overloaded versions, which let you specify a timeout to wait instead of waiting indefinitely.

Completion Callbacks

Instead of blocking, waiting, or polling for an asynchronous call to complete, WCF offers another programming model altogethercompletion callbacks. The client provides WCF with a method and requests that WCF will call that method back when the asynchronous method completes. The client can provide a callback instance method or static method and have the same callback method handle completion of multiple asynchronous calls. When the asynchronous method 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 asyncResult);

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

Figure demonstrates asynchronous call management by using a completion callback.

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);
   }
   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 object returned from Begin<Operation>( ) because when WCF calls the completion callback, WCF provides the IAsyncResult object as a parameter. Because WCF provides a unique IAsyncResult 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:

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

Note that the anonymous method sets 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 these 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. WCF asynchronous calls management using callback completion methods fits into such an architecture like a hand in a glove. 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 callback 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. You need to worry about synchronizing between client-side threads and the worker thread from the pool, and, potentially, synchronization between multiple worker threads all calling concurrently into the completion callback method to handle their respective asynchronous call completion. 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 object 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: in all the other programming models, it is up to you to manage the IAsyncResult object, and managing an additional container is not that much of an added liability. 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.

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

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)asyncResult.AsyncState;
      Debug.Assert(asyncState == 4);

      int sum = m_Proxy.EndAdd(result);
   }
   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);
      proxy.Close( );
   }
   void OnCompletion(IAsyncResult result)
   {
      CalculatorClient proxy = asyncResult.AsyncState as CalculatorClient;
      Debug.Assert(proxy != null);

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

Completion callback synchronization context

The completion callback may require some thread(s) affinity, to run in a particular synchronization context. This is especially the case if the completion callback needs to update some UI about the result of the asynchronous invocation. Unfortunately, you must manually marshal the call from the completion callback to the correct synchronization context, using any of the techniques described previously. Figure demonstrates such a completion callback that interacts directly with its containing form, assuring that the UI update will be on the UI synchronization context.

Relying on completion callback synchronization context

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

   public MyClient()
   {
      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( );
   }
}

One-Way Asynchronous Operations

There is little sense in trying to invoke a one-way operation asynchronously, because one of the main features of asynchronous calls is retrieving and correlating a reply message, and yet no such message is available with a one-way call. If you do invoke a one-way operation asynchronously, End<Operation>( ) will never block, and no exceptions will ever be thrown. If a completion callback is provided for an asynchronous invocation of a one-way operation, the callback is called immediately after returning from Begin<Operation>( ). 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.

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 it may be 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 the callback 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.

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


Cleaning Up After End<Operation>

Whenever calling Begin<Operation>( ), the returned IAsyncResult has a reference to a single WaitHandle object, accessible via the AsyncWaitHandle property. Calling End<Operation>( ) on that object does 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's ability to collect those handles, resulting with a resource leak. To compensate, you can explicitly close that handle after calling End<Operation>( ). For example, using the same definitions as those in Figure:

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. First, well-designed transactions are of short duration, yet the main motivation for using asynchronous calls is the latency of the operations. Second, the client's ambient transaction will not flow by default 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 done in the background that could commit or abort independently of the transaction, and yet this will be the result of spawning an asynchronous operation call from within a transaction. 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 the 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 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 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.

Synchronous processing of an order


The downside to the workflow shown in Figure 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 then all hell will break loose. For example, the Order service might discover there were no items in the inventory matching the customer request while 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. To call the helper services asynchronously, the Store service should call only the Order service, which in turn should call the Shipment service only if the order processing was successful (see Figure), 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


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.