Asynchronous Web Service Invocation

In my last post I described various ways to perform work on a background thread in a WPF smart client application. Performing long running operations on the main application thread will freeze the user interface and yield a poor user experience. On the other hand any GUI elements must be manipulated on the main application thread and only on that thread. In this post I describe three different ways to make web service calls on a background thread using WCF client components.

First I created a simple, self-hosted WCF service implementing a simple calculator interface. The Windows SDK has several example implementations of this interface. Each of the implementation methods in my service sleeps for 10 seconds to simulate a long running process.

[ServiceContract(Namespace="http://patconroy.wordpress.com/service")]
public interface ICalculator
{
    [OperationContract]
    double Add(double n1, double n2);
    [OperationContract]
    double Subtract(double n1, double n2);
    [OperationContract]
    double Multiply(double n1, double n2);
    [OperationContract]
    double Divide(double n1, double n2);
}

Those of you running Vista will need to execute the following command as administrator to update the system URL ACL, substituting your own Windows domain and userid in the appropriate place.

netsh http add urlacl url=http://+:8000/ user=domain\userid

The client application example presents four different ways of calling the web service. The main windows has four radio buttons to select from amongst them.

  1. Synchronously on the GUI thread
  2. Asynchronously using BackgroundWorker
  3. Using the Asynchronous Programming Model
  4. Using a new .NET 3.5 event based model

The first of these is certainly the easiest to code. All work occurs on the main application thread, but herein lies a problem. The user interface is frozen while the application waits for the web service to return.

Using BackgroundWorker

The second technique is certainly an improvement. BackgroundWorker is used to launch a background thread that makes the web service call. The background thread waits for the web service to return then sets DoWorkEventArgs.Result. This Result is handed to the RunWorkerCompleted delegate on the GUI thread.

private static void CallServiceBackgroundWorker(Window1 win, double n1, double n2)
{
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += BackgroundWorker_DoWork;
    bw.RunWorkerCompleted += win.BackgroundWorker_RunWorkerCompleted;
    bw.RunWorkerAsync(win);
}
// BackgroundWorker callbacks
static void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    Window1 win = (e.Argument) as Window1;
    e.Result = _calcClient.Add(win._data.Number1, win._data.Number2);
}
void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // This callback is made on the GUI thread
    UpdateResult((double)e.Result);
}

Do you see a problem here? Earlier I stated that “The background thread waits for the web service.” The application consumes a background thread while waiting for the web service to finish. This background thread could be utilized for something else while the web service call is outstanding. This isn’t a problem in a small example but could be a scalability killer in a larger real-world application.

Using the Asynchronous Programming Model

This last problem is solved by the Asynchronous Programming Model. The APM pervades the .NET framework. One can, for example, read a FileStream using this method. It works roughly like this.

void CallServiceAsyncPattern(Window1 win, double n1, double n2)
{
    IAsyncResult ar = obj.BeginXyz(param, new AsyncCallback(MyCallback), state);
}
void MyCallback(IAsyncResult ar)
{
    var result = obj.EndXyz(ar);
}

The names of the Begin and End methods will of course differ from class to class. The main application thread initiates the asynchronous operation by calling BeginXyz(). The framework itself then performs the operation on a background thread or better yet by using overlapped I/O. Overlapped I/O is a Windows OS feature that completes the I/O operation in the Windows kernel. An application callback is called when the operation completes. The callback occurs on a background thread.

The .NET web service proxy generator creates a Begin and End method for each of the service interface methods when the /async command line option is specified.

svcutil http://localhost:8000/service/calculator/mex /async

Or just check “Generate asynchronous operations” when creating a service reference in Visual Studio. This example client uses BeginAdd and EndAdd. AsyncCallback is called on a background thread when the web service operation completes. AsyncCallback cannot access the GUI directly, so it dispatches the result to the GUI thread.

private delegate void UpdateResultDelegate(double n);
private static void AsyncCallback(IAsyncResult ar)
{
    double result = _calcClient.EndAdd(ar);
    Window1 win = (ar.AsyncState as Window1);
    // Result must be dispatched to the GUI thread
    win.Dispatcher.Invoke(new UpdateResultDelegate(win.UpdateResult), result);
}
private static void CallServiceAsyncPattern(Window1 win, double n1, double n2)
{
    IAsyncResult ar = _calcClient.BeginAdd(n1, n2, AsyncCallback, win);
}

Using an event based model

A new event based asynchronous model was introduced in the framework version 3.5. The service proxy generator creates all the necessary delegate declarations and event argument classes. It also adds a completed event and an XyzAsync() method to the proxy class for each of the web service methods. Specify /tcv:version35 in addition to /async on the svcutil command line to generate this additional code. This is also done automatically in Visual Studio when “Generate asynchronous operations” is checked.

The event model is an improvement over APM because the completed event is called on the same thread that initially made the asynchronous service request. In the example here AddAsync() is called on the GUI thread, so the AddCompleted event handler is called on the GUI thread after the web service returns. The need to dispatch the result to the GUI thread has been eliminated. GUI state can be manipulated within the event handler, so coding is much easier than with APM.

Event handlers must be set on the service proxy instance prior to making any service requests. This example only uses the Add service method, so only an AddCompleted event handler is set. CalculatorClient also includes SubtractCompleted, MultiplyCompleted and DivideCompleted event handlers.

private static CalculatorClient _calcClient;
static Window1()
{
    _calcClient = new CalculatorClient();
    _calcClient.AddCompleted +=new EventHandler<AddCompletedEventArgs>(CalcClient_AddCompleted);
}
private static void CalcClient_AddCompleted(object sender, AddCompletedEventArgs e)
{
    // This callback is made on the GUI thread
    (e.UserState as Window1).UpdateResult(e.Result);
}

Initiating an asynchronous call to Add is simply a matter of calling CalculatorClient.AddAsync(). Note that the code generator also created SubtractAsync, MultiplyAsync and DivideAsync methods.

private static void CallServiceNewAsync(Window1 win, double n1, double n2)
{
   _calcClient.AddAsync(n1, n2, win);
}

All source code for this example is located here.

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




January 2009
M T W T F S S
« Dec   Feb »
 1234
567891011
12131415161718
19202122232425
262728293031  
I am a part of all that I have met;
Yet all exprience is an arch whitherthro'
Gleams that untravell'd world, whose margin fades
For ever and for ever when I move.
How dull it is to pause, to make an end,
To rust unburnish'd, not to shine in use!
Alfred, Lord Tennyson

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: