WCF + Tasks
In WCF we can use the APM pattern to create an asynchronous client. For example, consider the following service contract:
[ServiceContract]
public interface IHello
{
[OperationContract]
string Greet(string name);
}
An APM-enabled version of this interface would look like this:
[ServiceContract(Name = "IHello")]
public interface IHelloApm
{
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginGreet(string name, AsyncCallback asyncCallback, object asyncState);
string EndGreet(IAsyncResult asyncResult);
}
WCF’s client can automatically generate a proxy that implements this interface (e.g. by inheriting from ClientBase
However, in .NET 4.0, we now use Tasks, which provide a far more convenient API than APM. Fortunately, there’s an easy way to convert one to the other, by using the FromAsync methods in TaskFactory. This would require you to:
- Create the above APM interface.
- Create a client class.
- Wrap each method pair (Begin & End) in a method that calls FromAsync and returns a Task.
I decided this was way too much of a hassle, and apparently the good folks at Microsoft thought along the same lines. One of the samples in the Async CTP contains an IWsdlImportExtension (called TaskWsdlImportExtension) that causes SvcUtil (the same mechanism used by “Add Service Reference” in VS) to generate methods that return Tasks. If you’re using SvcUtil to generate your proxies, you’re good to go. You can use the sample generator even without relying on the CTP itself.
But what if you write your own interfaces? Wouldn’t it be nice if you could simply create the following interface and have the runtime figure it out for you?
[ServiceContract(Name = "IHello")]
public interface IHelloAsync
{
[OperationContract]
Task<string> Greet(string name);
}
Side (but important) note: You may ponder – why not just use Task.Factory.StartNew() method and pass a delegate that calls the original (synchronous) method? Because then you’d be wasting a thread that will be kept waiting for I/O! .NET utilizes I/O Completion Ports when you use the APM methods, which provides an efficient way of waiting for I/O to complete.
Introducing TaskClient
TaskClient is very much like ClientBase (in fact, it implements the same interface, ICommunicationObject), only it uses code generation (Reflection Emit) to generate both the async interface and a class that implements the above (IHelloAsync) interface, by invoking FromAsync for each method. Sample usage:
var taskClient = new TaskClient<IHelloAsync>();
taskClient.Channel.Greet(“Seattle”).ContinueWith(t => Console.WriteLine(t.Result));
Channel is IHelloAsync, and Greet returns a Task. We use a continuation to write the result to the console. When combined with Async CTP’s await keyword, this makes calling services that much easier.
The generated class looks something like this:
public class HelloAsyncClient : IHelloAsync
{
private readonly Func<IHelloApm> _channel; </p>
public HelloAsyncClient(Func<IHelloApm> channel)
{
_channel = channel;
}
public Task<string> Greet(string name)
{
IHelloApm channel = _channel();
return Task.Factory.FromAsync(
new Func<string, AsyncCallback, object, IAsyncResult>(channel.BeginGreet),
new Func<IAsyncResult, string>(channel.EndGreet), name, null, TaskCreationOptions);
}
public TaskCreationOptions TaskCreationOptions { get; set; }
}
Note that the channel is lazily evaluated on each call. This allows for more sophisticated channel management options.
Known limitations:
- TaskClient currently won’t handle overloaded methods in the interface.
- Ref/Out parameters are not supported (nor will they be; they do not make sense in async interfaces).
- The interface must be public.
A word of caution: While the code attached to this post works, it wasn’t thoroughly tested. So, if you would like to use this in a production environment, we are planning on making it available in a future release WCF Contrib. This is a well established library of WCF extensions. I suggest checking it out, and waiting for more news.
Attachment: ServiceModelTasks.zip