From sync to async: network I/O

Spread the love

Converting from a fully synchronous design to an asynchronous one can be quite an ordeal. Unlike other types of design changes which can be safely contained, going async necessarily touches essentially every module and function chain that deals with I/O. (This is why it’s a good reason to think async right from the start!)

So let’s say you’re making the jump and taking a legacy sync application to the modern async era. Use the following guidance to replace blocking network code with non-blocking patterns.

A note on “legacy” async patterns

Chances are there is already an async API for the job you are trying to do, though it won’t necessarily be Task-based. The asynchronous programming model (APM) and the event-based asynchronous pattern (EAP) are the prevailing asynchronous models used by pre-.NET 4.0 APIs. Where possible, prefer the APM since it is easier to deal with when converting to a Task-based pattern. To do the conversion, it is as simple as calling Task.Factory.FromAsync. There are many overloads, but make sure you always use the methods that accept a delegate for both the Begin and End method. In particular, the overload that takes a raw IAsyncResult for Begin is unnecessarily expensive and sub-optimal since it must perform a thread pool wait rather than register a completion callback. This code sample demonstrates a couple common cases of converting from APM to Task:

// Translate from Begin/EndAccept to Task-based async using overload that accepts
// a 'state' object to prevent implicit capture (slight perf optimization).
private static Task<Socket> AcceptSocketAsync(Socket socket)
{
    return Task.Factory.FromAsync(
        (c, s) => ((Socket)s).BeginAccept(c, s),
        r => ((Socket)r.AsyncState).EndAccept(r),
        socket);
}

// Similar to the above, but also passing an input parameter as 'arg1'.
private static Task<Socket> AcceptSocketAsync(Socket socket, int receiveSize)
{
    return Task.Factory.FromAsync(
        (i, c, s) => ((Socket)s).BeginAccept(i, c, s),
        r => ((Socket)r.AsyncState).EndAccept(r),
        receiveSize,
        socket);
}

If you really need to convert from EAP to Task, follow Stephen Toub’s guidance in his blog post “Tasks and the Event-based Asynchronous Pattern.”

Sockets

For raw sockets (System.Net.Socket), use the classic async programming model Begin/End methods (e.g. BeginAccept) in place of their sync equivalents (e.g. Accept). Note that an event-based async model is provided for Socket (e.g. see AcceptAsync) but it is generally easier to use the Begin/End methods along with Task.Factory.FromAsync as demonstrated above.

HTTP

If you currently use System.Net.WebClient, consider switching to System.Net.Http.HttpClient which provides a more “modern” experience with first-class Task-based APIs. If you can’t make the switch right away, then use the XxxTaskAsync methods on WebClient (e.g. UploadStringTaskAsync).

WCF

The base WCF abstraction ICommunicationObject has a fully functional APM interface (BeginOpen, etc.). In addition, starting in .NET 4.5, WCF supports client-side async contracts using Task and XxxAsync signatures. Example:

// Original sync contract
[ServiceContract]
public interface IMyService
{
    [OperationContract]
    string DoSomething(int input);
}

// Async version of the same contract. Note that the contract names
// must match; here we explicitly use the 'IMyService' name implicitly
// assigned to the above contract.
[ServiceContract(Name = "IMyService")]
public interface IMyServiceAsync
{
    [OperationContract(AsyncPattern = true)]
    Task<string> DoSomethingAsync(int input);
}

As you can see, converting network I/O from sync to async is relatively painless (aside from code churn). In a later post, I will cover a few more common situations when transitioning to non-blocking patterns.

One thought on “From sync to async: network I/O

  1. Pingback: From sync to async: SQL | WriteAsync .NET

Leave a Reply

Your email address will not be published. Required fields are marked *