Wrapping Other Asynchronous Patterns in Awaitable Tasks
Writing asynchronous code in .NET framework 4.5 is pure joy thanks to task-based asynchronous pattern (TAP) and the async
/await
syntactic sugar. Before we got to this point .NET introduced two other asynchronous programming patterns:
- Asynchronous programming model (APM) was the first one. You will recognize it by the
Begin*
andEnd*
methods for each asynchronous operation. - Event-based asynchronous pattern (EAP) was introduced in .NET framework 2.0. Asynchronous methods in this pattern have the
Async
suffix and require corresponding events which are triggered once the operation is complete.
Although many APIs have been updated since the introduction of TAP to provide task based asynchronous methods which can be used with async
and await
, occasionally you will still encounter operations in APM or EAP without a TAP equivalent. Fortunately .NET framework provides helpers which will make wrapping older style asynchronous operations into tasks much easier.
APM operations can be wrapped using TaskFactory.FromAsync
. It needs to be provided with the Begin*
and End*
method delegates, as well as all the input parameters for the Begin*
method. The returned Task
can be directly awaited to follow up the call with any cleanup operations that are required. Below is a sample wrapper for the Stream.BeginRead
method (.NET framework 4.5 already provides a task-based alternative). More information about the wrapping is available on MSDN.
private async Task<byte[]> ReadAsync(string filename)
{
var fileInfo = new FileInfo(filename);
using (var stream = new FileStream(filename, FileMode.Open))
{
var buffer = new byte[fileInfo.Length];
await Task<int>.Factory.FromAsync(stream.BeginRead,
stream.EndRead, buffer, 0, buffer.Length, null);
return buffer;
}
}
Wrapping EAP operations is a bit more complex. In this case TaskCompletionSource<T>
needs to be used. Its methods SetException
, SetCanceled
and SetResult
allow the forwarding of callback event argument properties to the task created by TaskCompletionSource
. It will be easier to understand by reading the example below, wrapping WebClient.DownloadStringAsync
(.NET framework 4.5 again provides a task-based alternative). Refer to MSDN for more details.
private Task<string> DownloadStringAsync(Uri uri)
{
var taskSource = new TaskCompletionSource<string>();
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, args) =>
{
try
{
if (args.Cancelled)
{
taskSource.SetCanceled();
}
else if (args.Error != null)
{
taskSource.SetException(args.Error);
}
else
{
taskSource.SetResult(args.Result);
}
}
finally
{
webClient.Dispose();
}
};
webClient.DownloadStringAsync(uri);
return taskSource.Task;
}
Thanks to these wrappers there really is no reason to implement asynchronous calls in any other way than by using async
and await
, unless you need to support older framework versions.