Skip to content

Instantly share code, notes, and snippets.

@AnthonySteele
Last active January 17, 2016 22:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AnthonySteele/44804be39f19a871b0a6 to your computer and use it in GitHub Desktop.
Save AnthonySteele/44804be39f19a871b0a6 to your computer and use it in GitHub Desktop.
About async handlers

##Setup

Many people have the misconception that async and sync code are mutually exclusive. This is not the case. It's easier than it seems!

If you have a method on an interface for async use, then the implementation can have zero or more awaits, and the case with zero awaits covers synchronous implementations. The code below will make this clear.

We have the following example contract that models a processor of messages:

    public class Request
    {
        public string Data { get; set; }
    }

    public class Response
    {
        public int Data { get; set; }
    }

    public interface IAsyncHandler
    {
        Task<Response> Process(Request request);
    }

NB: the method is never declared async on the interface; async is a marker of how the implementation works, and an interface method is not an implementation. Classes that implement the interface will usually - but not always - declare the implmenting method as async as we will see below.

I used a test harness like this to run the code and verify that it works:

    class Program
    {
        static void Main(string[] args)
        {

            var task = AsyncMain();
            task.Wait();
            Console.ReadLine();

        }

        private static async Task AsyncMain()
        {
            IAsyncHandler handler = MakeHandler();

            var response = await handler.Process(new Request { Data = "foo" });

            Console.WriteLine("response is " + response.Data);
        }

        private static IAsyncHandler MakeHandler()
        {
            return new DelayHandler();
        }
    

Example Handlers

Now how can we implement IAsyncHandler ? Here is a simple handler with exactly 1 await:

    public class DelayHandler : IAsyncHandler
    {
        public async Task<Response> Process(Request request)
        {
            const int MillisecondsDelay = 1500;
            await Task.Delay(MillisecondsDelay);
            return new Response { Data = MillisecondsDelay };
        }
    }

This works.

And here is a handler that only sometimes awaits anything. You might say that it has on average 0.5 awaits in the async method. The compiler handles this fine and doen't do any expensive task scheduling in the simple case without any awaits being hit:

    public class MaybeDelayHandler : IAsyncHandler
    {
        public async Task<Response> Process(Request request)
        {
            const int MillisecondsDelay = 1500;
            if (request.Data.Length > 3)
            {
                await Task.Delay(MillisecondsDelay);
            }
            return new Response { Data = MillisecondsDelay };
        }
    }

And here is a handler with no awaits:

    public class NoDelayHandler : IAsyncHandler
    {
        public async Task<Response> Process(Request request)
        {
            return new Response { Data = 0 };
        }
    }

This works: You dont' have to have awaits in an async method. But it has a compiler warning: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Note that the compiler warning is there because of the async modifier on the implementing method not because of the interface type. When implementing an interface, you might remove async entirely, so here's a handler with no async and no compiler warning:

    public class NoAsyncHandler : IAsyncHandler
    {
        public Task<Response> Process(Request request)
        {
            return Task.FromResult(new Response { Data = 0 });
        }
    }

Implications for JustSaying

How does this apply to JustSaying? We greatly want the handlers to be async. Most or all handlers that I have seen could and should be async since they do "naturally async" things like retrieving data from http or a database. i.e. they are IO-bound. but since the handler interface contract is currently sync, they are forced to busy-wait for results. Changing this could break lots of client code.

We have a choice of how to do this:

A: keep two sets of code for e.g. IHandler and IAsyncHandler and all associated classes. This is not simple, there arearound a dozen classes that would be duplicated, and would we be working with two collections of handlers, two logic paths, etc.

B: prefer async. This involves breaking changes and therefor a new major version, and assuming async by default. Sync handlers can be fairly trivially shimmed in e.g.

    public interface ISyncHandler
    {
        Response Process(Request request);
    }

    public class ShimHandler : IAsyncHandler
    {
        private readonly ISyncHandler _syncHandler;

        public ShimHandler(ISyncHandler syncHandler)
        {
            _syncHandler = syncHandler;
        }

        public Task<Response> Process(Request request)
        {
            return Task.FromResult(_syncHandler.Process(request));
        }
    }

The sync handlers won't quite be first-class citizens but they would run OK. I am fine with this and prefer this option. Most handlers are IO-bound and are better off async; and the few that are not IO-bound should be already fast enough that the overhead of Task.FromResult won't make them slow.

Since JustSaying is a nuget package, consumers can chose when to upgrade. IMHO we can't ignore async, it is on the way forward. Key parts of ASP.Net are going to be "async by default" in future, so it's worth understanding it and working that way too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment