Tag Archives: async

Avoid await in Foreach

Five months ago, I wrote my C# Asynchronous Programming series, and part of that was an article about Common Mistakes in Asynchronous Programming with .NET. As it turns out, I missed a really common mistake in that article.

        public async Task RunAsync()
        {
            foreach (var x in new[] { 1, 2, 3 })
            {
                await DoSomethingAsync(x);
            }
        }

Whenever I see an await within a foreach (or other looping construct), I tend to get suspicious. That’s because a lot of the time, an external service (Web API, Redis, or whatever) is called repeatedly with different parameters. Imagine that DoSomethingAsync() in the above code performs an HTTP GET request, passing in x as a querystring parameter.

The above could potentially be optimised to run the requests in parallel, as described in Patterns for Asynchronous Composite Tasks in C#. But since each asynchronous call is being awaited, this has the effect of waiting for each request-response cycle to complete before starting the next one.

To illustrate the point, we can implement DoSomethingAsync() as a simple delay:

        private async Task DoSomethingAsync(int x)
        {
            Console.WriteLine($"Doing {x}... ({DateTime.Now :hh:mm:ss})");
            await Task.Delay(2000);
            Console.WriteLine($"{x} done.    ({DateTime.Now :hh:mm:ss})");
        }

Let’s run that:

That’s six seconds just to run three two-second delays, which did not depend on each other and which thus could have been run in parallel. In fact, let’s now change the code to do that:

        public async Task RunAsync()
        {
            var tasks = new List<Task>();

            foreach (var x in new[] { 1, 2, 3 })
            {
                var task = DoSomethingAsync(x);
                tasks.Add(task);
            }

            await Task.WhenAll();
        }

…and run it again:

That’s 2-3 seconds, which is much better. Note though that the operations have completed in a different order from that in which they were started; this is why it’s important that they don’t depend on each other.

Do you think that’s a lot of code? No problem. We can make it more concise, with some help from LINQ.

        public async Task RunAsync()
        {
            var tasks = new[] { 1, 2, 3 }.Select(DoSomethingAsync);
            await Task.WhenAll(tasks);
        }

Having said all this, it is not good to be hasty and declare war against all awaits in foreaches, because there are indeed legitimate cases for that. One example is when you have a list of commands which conform to the same interface, and they must be executed in sequence. This is perfectly fine:

        public interface ICommand
        {
            Task ExecuteAsync();
        }

        public async Task ExecuteAsync(List<ICommand> commands)
        {
            foreach (var command in commands)
            {
                await command.ExecuteAsync();
            }
        }

When order of operations is important, running this sort of scenario in parallel can yield unexpected results.

My hope is that this will at least help to quickly identify potential performance bottlenecks due to an improper use of asynchrony. await in foreach should be eyed suspiciously as a potential code smell, but as with everything else, there is no hard and fast rule and it is perfectly fine if used correctly.

Asynchronous RabbitMQ Consumers in .NET

It’s quite common to do some sort of I/O operation (e.g. REST call) whenever a message is consumed by a RabbitMQ client. This should be done asynchronously, but it’s not as simple as changing the event handling code to async void.

In “The Dangers of async void Event Handlers“, I explained how making an event handler async void will mess up the message order, because the dispatcher loop will not wait for a message to be fully processed before calling the handler on the next one.

While that article provided a workaround that is great to use with older versions of the RabbitMQ Client library, it turns out that there is an AsyncEventingBasicConsumer as from RabbitMQ.Client 5.0.0-pre3 which works great for asynchronous message consumption.

AsyncEventingBasicConsumer Example

First, we need to make sure that the RabbitMQ client library is installed.

Install-Package RabbitMQ.Client

Then, we can set up a publisher and consumer to show how to use the AsyncEventingBasicConsumer. Since this is just a demonstration, we can have both in the same process:

        static void Main(string[] args)
        {
            var factory = new ConnectionFactory() { DispatchConsumersAsync = true };
            const string queueName = "myqueue";

            using (var connection = factory.CreateConnection())
            using (var channel = connection.CreateModel())
            {
                channel.QueueDeclare(queueName, true, false, false, null);

                // consumer

                var consumer = new AsyncEventingBasicConsumer(channel);
                consumer.Received += Consumer_Received;
                channel.BasicConsume(queueName, true, consumer);

                // publisher

                var props = channel.CreateBasicProperties();
                int i = 0;

                while (true)
                {
                    var messageBody = Encoding.UTF8.GetBytes($"Message {++i}");
                    channel.BasicPublish("", queueName, props, messageBody);
                    Thread.Sleep(50);
                }
            }
        }

There is nothing really special about the above code except that we’re using AsyncEventingBasicConsumer instead of EventingBasicConsumer, and that the ConnectionFactory is now being set up with a suspicious-looking DispatchConsumersAsync property set to true. The ConnectionFactory is using defaults, so it will connect to localhost using the guest account.

The message handler is expected to return Task, and this makes it very easy to use proper asynchronous code:

        private static async Task Consumer_Received(object sender, BasicDeliverEventArgs @event)
        {
            var message = Encoding.UTF8.GetString(@event.Body);

            Console.WriteLine($"Begin processing {message}");

            await Task.Delay(250);

            Console.WriteLine($"End processing {message}");
        }

The messages are indeed processed in order:

How to Mess This Up

Remember that DispatchConsumersAsync property? I haven’t really found any documentation explaining what it actually does, but we can venture a guess after a couple of experiments.

First, let’s keep that property, but use a synchronous EventingBasicConsumer instead (which also means changing the event handler to have a void return type). When we run this, we get an error:

It says “In the async mode you have to use an async consumer”. Which I suppose is fair enough.

So now, let’s go back to using an AsyncEventingBasicConsumer, but leave out the DispatchConsumersAsync property:

var factory = new ConnectionFactory();

This time, you’ll see that the the event handler is not firing (nothing is being written to the console). The messages are indeed being published, and the queue is remaining at zero messages, so they are being consumed (you’ll see them accumulate if you disable the consumer).

This is actually quite dangerous, yet there is no error like the one we saw earlier. It means that if a developer forgets to set that DispatchConsumersAsync property, then all messages are lost. It’s also quite strange that the choice of how to dispatch messages to the consumer (i.e. sync or async) is a property of the connection rather than the consumer, although presumably it would be a result of some internal plumbing in the RabbitMQ Client library.

Summary

AsyncEventingBasicConsumer is great for having pure asynchronous RabbitMQ consumers, but don’t forget that DispatchConsumersAsync property.

It’s only available since RabbitMQ.Client 5.0.0-pre3, so if you’re on an older version, use the workaround described in “The Dangers of async void Event Handlers” instead.

Abstracting RabbitMQ RPC with TaskCompletionSource

I recently wrote about TaskCompletionSource, a little-known tool in .NET that is great for transforming arbitrary asynchrony into the Task-Based Asynchronous Pattern. That means you can hide the whole thing behind a simple and elegant async/await.

In this article, we’ll see this in practice as we implement the Remote Procedure Call (RPC) pattern in RabbitMQ. This is a fancy way of saying request/response, except that it all happens asynchronously! That’s right. No blocking.

The source code for this article is in the RabbitMqRpc folder at the Gigi Labs BitBucket Repository.

The RabbitMQ.Client NuGet package is necessary to make this code work. The client is written using an asynchronous Main() method, which requires at least C# 7.1 to compile.

RabbitMQ RPC Overview

You can think of RPC as request/response communication. We have a client asking a server to process some input and return the output in its response. However, this all happens asynchronously. The client sends the request on a request queue and forgets about it, rather than waiting for the response. Eventually, the server will (hopefully) process the request and send a response message back on a response queue.

The request and response can be matched on the client side by attaching a CorellationId to both the request and the response.

In this context, we don’t really talk about publishers and consumers, as is typical when talking about messaging frameworks. That’s because in order to make this work, both the client and the server must have both a publisher and a consumer.

Client: Main Program

For our client application, we’ll have the following main program code. We will implement an RpcClient that will hide the request/response plumbing behind a simple Task that we then await:

        static async Task Main(string[] args)
        {
            Console.Title = "RabbitMQ RPC Client";

            using (var rpcClient = new RpcClient())
            {
                Console.WriteLine("Press ENTER or Ctrl+C to exit.");

                while (true)
                {
                    string message = null;

                    Console.Write("Enter a message to send: ");
                    using (var colour = new ScopedConsoleColour(ConsoleColor.Blue))
                        message = Console.ReadLine();

                    if (string.IsNullOrWhiteSpace(message))
                        break;
                    else
                    {
                        var response = await rpcClient.SendAsync(message);

                        Console.Write("Response was: ");
                        using (var colour = new ScopedConsoleColour(ConsoleColor.Green))
                            Console.WriteLine(response);
                    }
                }
            }
        }

The program continuously asks for input, and sends that input as the request message. The server will process this message and return a response. Note that we are using the ScopedConsoleColour class from my “Scope Bound Resource Management in C#” article to colour certain sections of the output. Here is a taste of what it will look like:

While this console application only allows us to send one request at a time, the underlying approach is really powerful with APIs that can concurrently serve a multitude of clients. It is asynchronous and can scale pretty well, yet the consuming code sees none of the underlying complexity.

Client: Request Sending

The heart of this abstraction is the RpcClient class. In the constructor, we set up the typical plumbing: create a connection, channel, queues, and a consumer.

    public class RpcClient : IDisposable
    {
        private bool disposed = false;
        private IConnection connection;
        private IModel channel;
        private EventingBasicConsumer consumer;
        private ConcurrentDictionary<string,
            TaskCompletionSource<string>> pendingMessages;

        private const string requestQueueName = "requestqueue";
        private const string responseQueueName = "responsequeue";
        private const string exchangeName = ""; // default exchange

        public RpcClient()
        {
            var factory = new ConnectionFactory() { HostName = "localhost" };

            this.connection = factory.CreateConnection();
            this.channel = connection.CreateModel();

            this.channel.QueueDeclare(requestQueueName, true, false, false, null);
            this.channel.QueueDeclare(responseQueueName, true, false, false, null);

            this.consumer = new EventingBasicConsumer(this.channel);
            this.consumer.Received += Consumer_Received;
            this.channel.BasicConsume(responseQueueName, true, consumer);

            this.pendingMessages = new ConcurrentDictionary<string,
                TaskCompletionSource<string>>();
        }

        // ...
    }

A few other things to notice here:

  1. We are keeping a dictionary that allow us to match responses with the requests that generated them, based on a CorrelationId. We have already seen this approach in “TaskCompletionSource by Example“.
  2. This class implements IDisposable, as it has several resources that need to be cleaned up. While I don’t show the code for this for brevity’s sake, you can find it in the source code.
  3. We are not using exchanges here, so using an empty string for the exchange name allows us to use the default exchange and publish directly to the queue.

The SendAsync() method, which we saw being used in the main program, is implemented as follows:

        public Task<string> SendAsync(string message)
        {
            var tcs = new TaskCompletionSource<string>();
            var correlationId = Guid.NewGuid().ToString();

            this.pendingMessages[correlationId] = tcs;

            this.Publish(message, correlationId);

            return tcs.Task;
        }

Here, we are generating GUID to use as a CorrelationId, and we are adding an entry in the dictionary for this request. This dictionary maps the CorrelationId to a corresponding TaskCompletionSource. When the response arrives, it will set the result on this TaskCompletionSource, which enables the underlying task to complete. We return this underlying task, and that’s what the main program awaits. The main program will not be able to continue until the response is received.

In this method, we are also calling a private Publish() method, which takes care of the details of publishing to the request queue on RabbitMQ:

        private void Publish(string message, string correlationId)
        {
            var props = this.channel.CreateBasicProperties();
            props.CorrelationId = correlationId;
            props.ReplyTo = responseQueueName;

            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            this.channel.BasicPublish(exchangeName, requestQueueName, props, messageBytes);

            using (var colour = new ScopedConsoleColour(ConsoleColor.Yellow))
                Console.WriteLine($"Sent: {message} with CorrelationId {correlationId}");
        }

While this publishing code is for the most part pretty standard, we are using two particular properties that are especially suited for the RPC pattern. The first is CorrelationId, where we store the CorrelationId we generated earlier, and which the server will copy and send back as part of the response, enabling this whole orchestration. The second is the ReplyTo property, which is used to indicate to the server on which queue it should send the response. We don’t need it for this simple example since we are always using the same response queue, but this property enables the server to dynamically route responses where they are needed.

Server

The request eventually reaches a server which has a consumer waiting on the request queue. Its Main() method is mostly plumbing that enables this consumer to work:

        private static IModel channel;

        static void Main(string[] args)
        {
            Console.Title = "RabbitMQ RPC Server";

            var factory = new ConnectionFactory() { HostName = "localhost" };

            using (var connection = factory.CreateConnection())
            {
                using (channel = connection.CreateModel())
                {
                    const string requestQueueName = "requestqueue";
                    channel.QueueDeclare(requestQueueName, true, false, false, null);

                    // consumer

                    var consumer = new EventingBasicConsumer(channel);
                    consumer.Received += Consumer_Received;
                    channel.BasicConsume(requestQueueName, true, consumer);

                    Console.WriteLine("Waiting for messages...");
                    Console.WriteLine("Press ENTER to exit.");
                    Console.WriteLine();
                    Console.ReadLine();
                }
            }
        }

When a message is received, the Consumer_Received event handler processes the message:

        private static void Consumer_Received(object sender, BasicDeliverEventArgs e)
        {
            var requestMessage = Encoding.UTF8.GetString(e.Body);
            var correlationId = e.BasicProperties.CorrelationId;
            string responseQueueName = e.BasicProperties.ReplyTo;

            Console.WriteLine($"Received: {requestMessage} with CorrelationId {correlationId}");

            var responseMessage = Reverse(requestMessage);
            Publish(responseMessage, correlationId, responseQueueName);
        }

In this example, the server’s job is to reverse whatever messages it receives. Thus, each response will contain the same message as in the corresponding request, but backwards. This reversal code is taken from this Stack Overflow answer. Although trivial to implement, this serves as a reminder that there’s no need to reinvent the wheel if somebody already implemented the same thing (and quite well, at that) before you.

        public static string Reverse(string s)
        {
            char[] charArray = s.ToCharArray();
            Array.Reverse(charArray);
            return new string(charArray);
        }

Having computed the reverse of the request message, and extracted both the CorrelationId and ReplyTo properties, these are all passed to the Publish() method which sends back the response:

        private static void Publish(string responseMessage, string correlationId,
            string responseQueueName)
        {
            byte[] responseMessageBytes = Encoding.UTF8.GetBytes(responseMessage);

            const string exchangeName = ""; // default exchange
            var responseProps = channel.CreateBasicProperties();
            responseProps.CorrelationId = correlationId;

            channel.BasicPublish(exchangeName, responseQueueName, responseProps, responseMessageBytes);

            Console.WriteLine($"Sent: {responseMessage} with CorrelationId {correlationId}");
            Console.WriteLine();
        }

The response is sent back on the queue specified in the ReplyTo property of the request message. The response is also given the same CorrelationId as the request; that way the client will know that this response is for that particular request.

Client: Response Handling

When the response arrives, the client’s own consumer event handler will run to process it:

        private void Consumer_Received(object sender, BasicDeliverEventArgs e)
        {
            var correlationId = e.BasicProperties.CorrelationId;
            var message = Encoding.UTF8.GetString(e.Body);

            using (var colour = new ScopedConsoleColour(ConsoleColor.Yellow))
                Console.WriteLine($"Received: {message} with CorrelationId {correlationId}");

            this.pendingMessages.TryRemove(correlationId, out var tcs);
            if (tcs != null)
                tcs.SetResult(message);
        }

The client extracts the CorrelationId from the response, and uses it to get the TaskCompletionSource for the corresponding request. If the TaskCompletionSource is found, then its result is set to the content of the response. This causes the underlying task to complete, and thus the caller awaiting that task will be able to resume and work with the result.

If the TaskCompletionSource is not found, then we ignore the response, and there is a reason for this:

“You may ask, why should we ignore unknown messages in the callback queue, rather than failing with an error? It’s due to a possibility of a race condition on the server side. Although unlikely, it is possible that the RPC server will die just after sending us the answer, but before sending an acknowledgment message for the request. If that happens, the restarted RPC server will process the request again. That’s why on the client we must handle the duplicate responses gracefully, and the RPC should ideally be idempotent.” — RabbitMQ RPC tutorial

Demo

If we run both the client and server, we can enter messages in the client, one by one. The client publishes each message on the request queue and waits for the response, at which point it allows the main program to continue by setting the result of that request’s TaskCompletionSource.

Summary

What we have seen in this article is the same material I had explained in “TaskCompletionSource by Example“, but with a real application to RabbitMQ.

A TaskCompletionSource has an underlying Task that can represent a pending request. By giving each request an ID, you can keep track of it as the corresponding response should carry the same ID. A mapping between request IDs and TaskCompletionSource can easily be kept in a dictionary. When a response arrives, its corresponding entry in the dictionary can be found, and the Task can be completed. Any client code awaiting this Task may then resume.

TaskCompletionSource by Example

In this article, we’ll learn how to use TaskCompletionSource. It’s one of those tools which you will rarely need to use, but when you do, you’ll be glad that you knew about it. Let’s dive right into it.

Basic Usage

The source code for this section is in the TaskCompletionSource1 folder at the Gigi Labs BitBucket Repository.

Let’s create a new console application, and in Main(), we’ll have my usual workaround for running asynchronous code in a console application:

        static void Main(string[] args)
        {
            Run();
            Console.ReadLine();
        }

In the Run() method, we have a simple example showing how TaskCompletionSource works:

        static async void Run()
        {
            var tcs = new TaskCompletionSource<bool>();

            var fireAndForgetTask = Task.Delay(5000)
                                        .ContinueWith(task => tcs.SetResult(true));

            await tcs.Task;
        }

TaskCompletionSource is just a wrapper for a Task, giving you control over its completion. Thus, a TaskCompletionSource<bool> will contain a Task<bool>, and you can set the bool result based on your own logic.

Here, we are using TaskCompletionSource as a synchronization mechanism. Our main thread spawns off an operation and waits for its result, using the Task in the TaskCompletionSource. Even if the operation is not Task-based, it can set the result of the Task in the TaskCompletionSource, allowing the main thread to resume its execution.

Let’s add some diagnostic code so that we can understand what’s going on from the output:

        static async void Run()
        {
            var stopwatch = Stopwatch.StartNew();

            var tcs = new TaskCompletionSource<bool>();

            Console.WriteLine($"Starting... (after {stopwatch.ElapsedMilliseconds}ms)");

            var fireAndForgetTask = Task.Delay(5000)
                                        .ContinueWith(task => tcs.SetResult(true));

            Console.WriteLine($"Waiting...  (after {stopwatch.ElapsedMilliseconds}ms)");

            await tcs.Task;

            Console.WriteLine($"Done.       (after {stopwatch.ElapsedMilliseconds}ms)");

            stopwatch.Stop();
        }

And here is the output:

Starting... (after 0ms)
Waiting...  (after 41ms)
Done.       (after 5072ms)

As you can see, the main thread waited until tcs.SetResult(true) was called; this triggered completion of the TaskCompletionSource’s underlying task (which the main thread was awaiting), and allowed the main thread to resume.

Aside from SetResult(), TaskCompletionSource offers methods to cancel a task or fault it with an exception. There are also safe Try...() equivalents:

SDK Example

The source code for this section is in the TaskCompletionSource2 folder at the Gigi Labs BitBucket Repository.

One scenario where I found TaskCompletionSource to be extremely well-suited is when you are given a third-party SDK which exposes events. Imagine this: you submit an order via an SDK method, and it gives you an ID for that order, but not the result. The SDK goes off and does what it has to do to perhaps talk to an external service and have the order processed. When this eventually happens, the SDK fires an event to notify the calling application on whether the order was placed successfully.

We’ll use this as an example of the SDK code:

    public class MockSdk
    {
        public event EventHandler<OrderOutcome> OnOrderCompleted;

        public Guid SubmitOrder(decimal price)
        {
            var orderId = Guid.NewGuid();

            // do a REST call over the network or something
            Task.Delay(3000).ContinueWith(task => OnOrderCompleted(this,
                new OrderOutcome(orderId, true)));

            return orderId;
        }
    }

The OrderOutcome class is just a simple DTO:

    public class OrderOutcome
    {
        public Guid OrderId { get; set; }
        public bool Success { get; set; }

        public OrderOutcome(Guid orderId, bool success)
        {
            this.OrderId = orderId;
            this.Success = success;
        }
    }

Notice how MockSdk‘s SubmitOrder does not return any form of Task, and we can’t await it. This doesn’t necessarily mean that it’s blocking; it might be using another form of asynchrony such as the Asynchronous Programming Model or a messaging framework with a request-response fashion (such as RPC over RabbitMQ).

At the end of the day, this is still asynchrony, and we can use TaskCompletionSource to build a Task-based Asynchronous Pattern abstraction over it (allowing the application to simply await the call).

First, we start building a simple proxy class that wraps the SDK:

    public class SdkProxy
    {
        private MockSdk sdk;

        public SdkProxy()
        {
            this.sdk = new MockSdk();
            this.sdk.OnOrderCompleted += Sdk_OnOrderCompleted;
        }

        private void Sdk_OnOrderCompleted(object sender, OrderOutcome e)
        {
            // TODO
        }
    }

We then add a dictionary, which allows us to relate each OrderId to its corresponding TaskCompletionSource. Using a ConcurrentDictionary instead of a normal Dictionary helps deal with multithreading scenarios without needing to lock:

        private ConcurrentDictionary<Guid,
            TaskCompletionSource<bool>> pendingOrders;
        private MockSdk sdk;

        public SdkProxy()
        {
            this.pendingOrders = new ConcurrentDictionary<Guid,
                TaskCompletionSource<bool>>();

            this.sdk = new MockSdk();
            this.sdk.OnOrderCompleted += Sdk_OnOrderCompleted;
        }

The proxy class exposes a SubmitOrderAsync() method:

        public Task SubmitOrderAsync(decimal price)
        {
            var orderId = sdk.SubmitOrder(price);

            Console.WriteLine($"OrderId {orderId} submitted with price {price}");

            var tcs = new TaskCompletionSource<bool>();
            this.pendingOrders.TryAdd(orderId, tcs);

            return tcs.Task;
        }

This method calls the underlying SubmitOrder() in the SDK, and uses the returned OrderId to add a new TaskCompletionSource in the dictionary. The TaskCompletionSource’s underlying Task is returned, so that the application can await it.

        private void Sdk_OnOrderCompleted(object sender, OrderOutcome e)
        {
            string successStr = e.Success ? "was successful" : "failed";
            Console.WriteLine($"OrderId {e.OrderId} {successStr}");

            this.pendingOrders.TryRemove(e.OrderId, out var tcs);
            tcs.SetResult(e.Success);
        }

When the SDK fires a completion event, the proxy will remove the TaskCompletionSource from the pending order and set its result. The application awaiting the underlying task will resume and take a decision depending on the logic.

We can test this with the following program code in a console application:

        static void Main(string[] args)
        {
            Run();
            Console.ReadLine();
        }

        static async void Run()
        {
            var sdkProxy = new SdkProxy();

            await sdkProxy.SubmitOrderAsync(10);
            await sdkProxy.SubmitOrderAsync(20);
            await sdkProxy.SubmitOrderAsync(5);
            await sdkProxy.SubmitOrderAsync(15);
            await sdkProxy.SubmitOrderAsync(4);
        }

The output shows that the program did indeed wait for each order to complete before starting the next one:

OrderId 3e2d4577-8bbb-46b7-a5df-2efec23bae6b submitted with price 10
OrderId 3e2d4577-8bbb-46b7-a5df-2efec23bae6b was successful
OrderId e22425b9-3aa3-48db-a40f-8b8cfbdcd3af submitted with price 20
OrderId e22425b9-3aa3-48db-a40f-8b8cfbdcd3af was successful
OrderId 3b5a2602-a5d2-4225-bbdb-10642a63f7bc submitted with price 5
OrderId 3b5a2602-a5d2-4225-bbdb-10642a63f7bc was successful
OrderId ffd61cea-343e-4a9c-a76f-889598a45993 submitted with price 15
OrderId ffd61cea-343e-4a9c-a76f-889598a45993 was successful
OrderId b443462c-f949-49b9-a6f0-08bbbb82fe7e submitted with price 4
OrderId b443462c-f949-49b9-a6f0-08bbbb82fe7e was successful

Summary

Use TaskCompletionSource to adapt an arbitrary form of asynchrony to use Tasks, and enable elegant async/await usage.

Do not use it simply expose an asynchronous wrapper for a synchronous method. You should either not do that at all, or use Task.FromResult() instead.

If you’re concerned that the the asynchronous call might never resume, consider adding a timeout.

Common Mistakes in Asynchronous Programming with .NET

In the last few articles, we have seen how to work with asynchronous programming in C#. Although it is now easier than ever to write responsive applications that do asynchronous, non-blocking I/O operations, many people still use asynchronous programming incorrectly. A lot of this is due to confusion over usage of the Task class in .NET, which is used in multithreaded and parallel scenarios as well as asynchronous ones. To make matters worse, it is not obvious to everyone that these are actually different things.

So let’s address this concern first.

Update 16th October 2017: Several people have pointed out errors in this article with regards to the effect that blocking has on the CPU. I apologise for this, and have made corrections. Blocking does not hog the CPU, but prevents threads from doing other work while they wait. Thanks for clarifying the confusion, and I welcome any further corrections.

Update 8th March 2018: See also “Avoid await in Foreach” for another common mistake with async/await.

Asynchronous vs Multithreading etc

When we use a computer, many programs are running at the same time. Before the advent of multicore CPUs, this was achieved by having instructions from different processes (and eventually threads) running one at a time on the same CPU. These instructions are interleaved, and the CPU switches rapidly from one to another (in what we call a context switch), giving the illusion that they are running at the same time. This is concurrency.

CPUs with multiple cores have the additional ability of literally executing multiple intructions at the same time, which is parallel execution. Multithreading gives the developers to make full use of the available cores; without it, instructions from a single process would only be able to execute on a single core at a time.

“Tasks which are executing on distinct processors at any point in time are said to be running in parallel. It may also be possible to execute several tasks on a single processor. Over a period of time, the impression is given that they are running in parallel, when in fact, at any point in time, only one task has control of the processor. In this case, we say that the tasks are being performed concurrently, that is, their execution is being shared by the same processor.” — Practical Parallel Rendering, Chalmers et al, A K Peters, 2002.

In .NET, we can do parallel processing by using multithreading, or an abstraction thereof, such as the Task Parallel Library. Parallel processing is CPU-bound.

Parallel processing is often contrasted with distributed processing, where the computing resources are not physically tightly coupled. This is not, however, relevant to asynchronous programming, so we will not delve into it.

Operations that take a long time to execute will typically hold control of the thread in which they are running, in what we call a blocking operation. However, if these operations involve waiting for I/O to occur (e.g. waiting for results from a file, network or database), then the I/O could occur in a non-blocking fashion without holding the thread at all during the waiting time. We say that the I/O operation is asynchronous: the thread that is waiting for it does not actually wait, but may be reassigned to do other work until the I/O operation is complete.

Asynchronous non-blocking I/O is not enabled by multithreading. In fact, Stephen Cleary goes into detail about how this works in his excellent post, “There Is No Thread“. In brief, a mechanism known as I/O Completion Ports (IOCP) is used to notify a thread that its I/O request is ready; but that thread does not need to block (or indeed run at all) during the waiting time. This is what we enable when we do an asynchronous wait by means of the await keyword.

In order to write efficient code, it is fundamental to understand the nature of what the code is doing. Parallel CPU-based execution involves significant overheads in thread synchronization. It makes no sense to use Parallel.ForEach() for I/O-bound tasks, and many are also surprised to find that executing CPU-based tasks sequentially is often faster than doing them in parallel, especially when such tasks are fine-grained and do very trivial work. In such cases, the synchronization overheads dwarf the cost of executing that code directly on the CPU on a single thread.

See also: “Asynchronous and Concurrent Processing in Akka .NET Actors“, which has a section on Asynchronous vs Concurrent Processing using simple tasks.

The Dangers of Blocking with Asynchronous Code

Asynchronous programming has two main benefits: scalability and offloading. If you block, then you are hogging resources (i.e. threads) that could be better used elsewhere. In an ASP .NET context, this means that a thread cannot service other requests (hurts scalability). In a GUI context, it means that the UI thread cannot be used for rendering because it is busy waiting for a long-running operation (so the work should be offloaded).

There are several methods which are part of the Task API which block, such as Wait(), WaitAny() and WaitAll(). The Result property also has the effect of blocking until the task is complete. Stephen Cleary has a table in his Async and Await intro showing these blocking API calls and how to turn them into asynchronous calls. Best practice is to await the asynchronous equivalent of the blocking method

However, simply wasting threading resources is not the only problem with blocking. It is actually very easy to end up with a deadlock and stall your application. Stephen Cleary has an excellent explanation of how this happens, with two concise examples based on GUI applications (e.g. WPF or Windows Forms) and ASP .NET. I am only going to attempt to simplify the scenario and illustrate it with a diagram.

Consider the following code in a WPF application’s codebehind (MainWindow.xaml.cs):

        private Uri uri = new Uri("http://ip.jsontest.com/");

        public async Task WaitABit()
        {
            await Task.Delay(3000);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var task = WaitABit();
            task.Wait();
        }

This results in deadlock. Let’s see why:

  1. Button_Click() calls WaitABit() without awaiting it. This starts a task in fire-and-forget mode (so far).
  2. WaitABit() calls Task.Delay() and awaits its result asynchronously. The thread does not block, since the execution has gone into “I/O mode” (technically a delay isn’t really I/O, but the idea is the same).
  3. In the meantime, Button_Click() resumes execution, and calls Wait(), effectively blocking until the result of WaitABit() is ready.
  4. The delay completes.
  5. WaitABit() should resume, but the UI thread that it’s supposed to run on is already blocked.
  6. The deadlock occurs because the continuation of WaitABit() needs to run on the UI thread, but the UI thread is blocked waiting for the result of WaitABit().

Note: I intentionally haven’t simplified WaitABit() such that it just returns the delay rather than doing a single-line async/await, as it would not deadlock. Can you guess why?

This example has shown blocking of the UI thread, but the concept stretches beyond GUI applications. In order to fully understand what is happening, we need to understand what a SynchronizationContext is. In short, it’s an abstraction of a threading model within an application. A GUI application needs a single UI thread for rendering and updating GUI components (although you can use other threads, they cannot touch the GUI directly). An ASP .NET application, on the other hand, handles requests using the thread pool. The SynchronizationContext is the abstraction that allows us to use the same multithreaded and asynchronous programming models across applications with fundamentally different internal threading models.

As a result, GUI applications (e.g. Windows Forms and WPF) and ASP .NET applications (those targeting the full .NET Framework) can deadlock. ASP .NET Core applications don’t have a SynchronizationContext, so they will not deadlock. Console applications won’t normally deadlock because the task continuation can just execute on another thread.

The deadlock occurs because the SynchronizationContext (e.g. the UI thread in the above example) is captured and used for the task continuation. However, we can prevent this from happening by using ConfigureAwait(false):

        public async Task WaitABit()
        {
            await Task.Delay(3000).ConfigureAwait(false);
        }

The GUI application does not deadlock now, because a thread pool can be picked to execute the continuation. Once WaitABit() completes, then the blocking Wait() in Button_Click() can resume on the same UI thread where it started. Any modifications to UI elements would work fine.

While ConfigureAwait(false) is no replacement for doing async/await all the way, it does have its benefits. Capturing the SynchronizationContext incurs a performance penalty, so if it is not actually necessary, library code should avoid it by using ConfigureAwait(false). Also, if application code must block on an asynchronous call for legacy reasons, then ConfigureAwait(false) would avoid the resulting deadlocks.

Of course, the real fix for our deadlock example here is really as easy as this (without ConfigureAwait(false)):

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var task = WaitABit();
            await task;
        }

I’ve just replaced the blocking Wait() call with an await, and marked the method as async as a result.

Asynchronous Wrappers for Synchronous Methods

A lot of library APIs with asynchronous methods have pairs of synchronous and asynchronous methods, e.g. Read() and ReadAsync(), Write() and WriteAsync(), etc. If your library API is purely synchronous, then you should not expose asynchronous wrappers for the synchronous methods (e.g. by using Task.Run()).

Stephen Toub goes into detail on why this is a bad idea in the article linked above, but I think the following paragraph summarises it best:

“If a developer needs to achieve better scalability, they can use any async APIs exposed, and they don’t have to pay additional overhead for invoking a faux async API. If a developer needs to achieve responsiveness or parallelism with synchronous APIs, they can simply wrap the invocation with a method like Task.Run.” — Stephen Toub, “Should I expose asynchronous wrappers for synchronous methods?

Asynchronous Properties

You can’t use async/await in properties. You can use them indirectly via an asynchronous method, but it’s a rather weird thing to do.

“[You can’t use await i]nside of a property getter or setter. Properties are meant to return to the caller quickly, and thus are not expected to need asynchrony, which is geared for potentially long-running operations. If you must use asynchrony in your properties, you can do so by implementing an async method which is then used from your property.” — Stephen Toub, Async/Await FAQ

async void methods

async void methods should only be used for top-level event handlers. In “The Dangers of async void Event Handlers“, I explain the general dangers of async void methods (mainly related to not having a task that you can await), but I also demonstrate and solve the additional problem of async void event handlers interleaving while they run (which is problematic if you’re expecting to handle events in sequence, such as with a message queue).

    1. “There is no way for the caller to await completion of the method.
    2. “As a result of this, async void calls are fire-and-forget.
    3. “Thus it follows that async void methods (including event handlers) will execute in parallel if called in a loop.
    4. “Exceptions can cause the application to crash (see the aforementioned article by Stephen Cleary for more on this).”

— Daniel D’Agostino, The Dangers of async void Event Handlers

Summary

  1. Parallel is CPU-based. Asynchronous is I/O-based. Don’t mix the two. (Running asynchronous I/O tasks “in parallel” is OK, as long as you’re doing it following the proper patterns rather than using something like Parallel.Foreach().)
  2. Asynchronous I/O does not use any threads.
  3. Blocking affects scalability and can hold higher-priority resources (such as a UI thread).
  4. Blocking can also result in deadlocks. Prevent them by using async/await all the way if you can. ConfigureAwait(false) is useful in library code both for performance reasons and to prevent deadlocks resulting from application code that must block for legacy reasons.
  5. For asynchronous libraries, don’t expose synchronous wrappers. There is an overhead associated with it, and the client can decide whether it’s worth doing from their end.
  6. You can’t have asynchronous properties, except indirectly via asynchronous methods. Avoid this.
  7. async void is for event handlers. Even so, if ordering is important, beware of interleaving.