SyntaxHighlighter

Sunday 1 September 2013

Asynchronous WCF calls

This post was originally on my Posterous account, it's posted here without updating, some of the links my not work.

Update: I have created a CodeRush plugin to automate this which can be found https://bitbucket.org/azdlowry/cr_asyncwcfmethods

It is very easy to make asynchronous WCF calls without resorting to having a thread hanging around. Let's say you have a service contract like this:
[ServiceContract(Name = "MyService", Namespace = "urn:www.myurl.com/MyService/2011/06")]
public interface IMyService
{
    [OperationContract]
    MyRetunType FetchMyInformation(InfoId informationId);
}

To call this service asynchronously create another interface like this:
[ServiceContract(Name = "MyService", Namespace = "urn:www.myurl.com/MyService/2011/06")]
public interface IMyServiceAsync
{
    [OperationContract(AsyncPattern = true)]
    IAsyncResult BeginFetchMyInformation(InfoId informationId, AsyncCallback callback, object state);

    MyRetunType EndFetchMyInformation(IAsyncResult asyncResult);
}

Notes:
  1. The ServiceContract's Name and Namespace arguments must be identical to the original
  2. The service only needs to implement the original interface not the new async one. WCF only uses this new interface to generate the proxy.

The new interface can now be used in the normal way the .Net 1.0 Async pattern is used, or more easily with the .Net Task Parallel Library:
IMyServiceAsync channel = GetChannel();
Task task = Task.Factory.FromAsync(channel.BeginFetchMyInformation(id, null, null), asyncResult => channel.EndFetchMyInformation(asyncResult));

Note: It is very important that the same channel is used for both the Begin and the End, otherwise WCF will throw an exception on the End.

This will start the call and return immediately. You can then either wait on the result:
MyReturnType ret = task.Result;

Or use a continuation to act on the result once the task is complete:
task.ContinueWith(task => Information = task.Result, TaskScheduler.FromCurrentSynchronizationContext());

Note: The TaskScheduler.FromCurrentSynchronizationContext() argument is optional and can be used to ensure the continuation is performed on the UI thread. Make sure this is called on the UI thread in this case, the result can be kept and reused across multiple tasks.

You can also chain async WCF calls together:
Task<string> task = Task.Factory.FromAsync(channel.BeginFetchMyInformation(id, null, null), asyncResult => channel.EndFetchMyInformation(asyncResult))
    .ContinueWith(task => Task.Factory.FromAsync(channel.BeginParse(task.Result, null, null), asyncResult => channel.EndParse(asyncResult)))
    .Unwrap();

This will give you a Task that calls FetchMyInformation, then calls Parse with the result of FetchMyInformation. The Unwrap means you get a Task instead of a Task<Task>, and allows you to easily add more continuations to the chain of tasks. I would strongly advise wrapping the Task.Factory.FromAsync calls in another method, so your code is more readable.

Task also supports exception handling, cancellation and performing multiple tasks in parallel.MSDN Task Parallel Library provides more information.

No comments: