Monday, September 21, 2009

Calling a WCF Service Asynchronously from the Client

In ‘old school’ ASMX web services, the generated proxy allowed you to call a service method asynchronously by using the auto-generated Begin/End methods for the operation in question. This is important for client applications, to avoid blocking the UI thread when doing something as (potentially) slow as a network call.

WCF continues this approach: if you use ‘add service reference’ with ‘Generate Asynchronous Operations’ checked (in the Advanced dialog):

image

... or SvcUtil.exe with the /a (async) option, your client proxy will contain Begin/End methods for each operation, in addition to the original, synchronous version. So for an operation like:

        [OperationContract]

        string DoWork(int value);

…the proxy-generated version of the interface will contain two additional methods, representing the Asynchronous Programming Model (APM)-equivalent signature:

        [OperationContract(AsyncPattern=true)]

        IAsyncResult BeginDoWork(int value, AsyncCallback callback, object asyncState);

 

        string EndDoWork(IAsyncResult result); // No [OperationContract] here

All it takes then to call the operation is to call the relevant Begin method, passing in a callback that is used to process the result. The UI thread is left unblocked and your request is executed in the background.

You can achieve the same result[1] with a ChannelFactory, but it takes a bit more work. Typically you use the ChannelFactory directly when you are sharing interface types, in which case you only get Begin/End methods if they are defined on the original interface – the ChannelFactory can’t magically add them. What you can do, however, is create another copy of the interface, and manually implement the additional ‘async pattern’ signatures, as demonstrated above.

Either way, what’s really important to realise here is that the transport is still synchronous. What the Begin/End methods are doing is allowing you to ‘hand off’ the message dispatch to WCF, and be called back when the result returns. And this explains why we can pull the trick above where we change the client’s version of the interface and it all ‘just still works’: from WCF’s perspective the sync and begin/end-pair signatures are considered identical. The service itself has only one operation ‘DoWork’.

And the reverse applies too. If you have a service operation already defined following the APM - i.e. BeginDoWork / EndDoWork - unless you choose to generate the async overloads your client proxy is going to only contain the sync version: DoWork(). Your client is calling – synchronously – an operation expressly defined asynchronously on the server. Weird, yes? But (as we will see later), it doesn’t matter. It makes no difference to the server.

Take-home

Clients should call service operations using async methods, to avoid blocking the UI thread. Whether the service is or isn’t implemented asynchronously is completely irrelevant[2].

[1] For completeness I should point out there are actually two more ways to call the operation asynchronously, but it all amounts to the same thing:

  • The generated service proxy client also contains OperationAsync / OperationCompleted method/event pairs. This event-based approach is considered easier to use (I don’t see it personally). Note this is on the generated client, not on the proxy’s version of the interface, so you can’t do this with a ChannelFactory (unless you implement it yourself).
  • If you’re deriving from ClientBase directly you can use the protected InvokeAsync method directly. Hardcore! This is how the generated proxy client implements the Async / Completed pattern above.

[2] …and transparent, unless you’re sharing interface types

No comments:

Popular Posts