Intro

In this post of the concurrency series we will provide insights on asynchronous programming. So what is asynchrony? Succinctly, is about not blocking the thread that initiates an operation. The key difference between synchronous and asynchronous is that the latter can start a new operation before the first one completes.

For example, a user clicks on a button to fetch profile information from a database over the network. The UI thread executes the click event handler code. The event handler then will make a IO request asking for data to come back. In synchronous code the UI thread is blocked waiting for the network request to come back i.e. waste thread time. On the other hand, an asynchronous code will release the UI thread to go and do other stuff e.g. spin the progress indicator, and on request completion will notify the UI thread to resume execution where it originally stopped.

Async Thread Flow

Motivations

Parallel programming addresses CPU bound operations by involving multiple threads to increase throughput and CPU utilisation. Asynchronous programming focuses primarily on improving caller thread utilisation. Practically asynchronous programming used to make UI applications responsive and increase server applications scalability. In addition, .NET 4.5 has abstracted away a lot of the pain of dealing with asynchronous operations, as it is generally a complex area of programming. Some might call async/await a syntactic sugar because all the magic takes places at the compiler and run-time.

So far so good, but how asynchronous programming compares to multi-threading? Why not simply create few threads and run every operation on a separate thread, i.e. something similar to what we saw with parallelism. The problem here is that an IO bound operation would occupy the thread for the entire time needed to complete e.g. to download a web resource, whereas in such operation the thread is needed only to run the processing code. Whilst application is waiting for the asynchronous operation to complete it can utilise the freed thread to do other stuff.

“Human Computer Interaction (HCI) studies have shown that users don’t notice a slow application, as long as the interface is responsive, and preferably has an animated progress indicator.” – Alex Davies, Async in C# 5.0

Workflow of async/await

As you noticed async/await is the syntax to write asynchronous code in .NET. The Task acts as a future promise that will notify the caller once the awaited method completes (INotifyCompletion ).

The compiler generates a state machine under the hood for async/await.

Note: Ignore for a second ConfigureAwait, we will cover it soon.

Step 1: Enter this method when command is invoked e.g. button clicked. Calls data access method that returns Task.

Step 2: Run-time checks if webClient.DownloadDataTaskAsync Task flag IsCompleted is true. If not it saves the current state & captures context for future notification and returns to the caller.

Step 3: Run-time checks if imageBytesTask is completed, If not it saves the state for future notification and returns to the caller of ExecuteDownloadCommand i.e. framework.

Step 4: When webClient.DownloadDataTaskAsync completes it resumes where we left at Step 2. The result of the awaitable Task is assigned to the local variable downloadedBytes and return to the caller.

Step 5: Similarly the result of the awaitable imageBytesTask, the byte array, is used in the rest of the code; update UI.

Cancellation

To be able to cancel a task the code must support cancellation. Someone issues a cancel signal and someone has to respond to the signal. Cancellation can be requested but not enforced in .NET. A cancellable method must be designed to accept the CancellationToken structure

Not all .NET API async methods are take cancellation tokens, so in order to do so we have to implement our own cancellation.

Exception Handling

Error is handled different based on async method signature. Unhandled exceptions are thrown in the next GC.

Etiquette of async/await

Avoid async void

All methods using async/await we covered so far return Task or Task<T> except event handlers. From Task’s characteristics is that any any exception is thrown inside async Task method the exception is captured and placed on the Task object. With async void methods, any exceptions thrown will be raised directly on the active SynchronizationContext

Methods return async void are difficult to unit test. Generally speaking unit testing is trickier for asynchronous methods and it depends on test harness used.

Async all the way

This is a serious consideration before starting refactoring areas of your code base to benefit from asynchrony. In a layered architecture application (Presentation, Business, and Data Access) async/await method implementation must penetrate top to bottom. In other words avoid mixing synchronous with asynchronous calls in order to avoid dead locks and error reduce handling complexity.

Configure context

Before awaiting an async method the current context is captured, and the workflow resumes on the same context once the task completes. The context of a UI application is the main UI thread, and a for a web app is the ASP.NET request context. Note console application do not have Synchronisation Context. In order to avoid deadlocks is best to not continue on a captured context i.e. ConfigureAwait(continueOnCapturedContext: false)

For detailed explanation of async/await best practices check this post.

Mixing Parallel & Asynchronous

So far we looked at IO bound operations, however you could use await with CPU & IO bound operations.

Handling Reentrancy

When dealing with asynchronous code in a UI applications managing reentrancy becomes vital in order to avoid undesired behaviour. With reentrancy we mean calling the async code before it completes. Because the UI is responsive when using asynchronous calls a user for example could still click a Run button again and again before even the first run have finished. Here are ways to handle reentrancy that vary in their complexity from simple to complex:

  1. Disable the UI trigger, i.e. disable the run button at the start of the operation and re-enable it at the end. – Simple
  2. Cancel and Restart. You would have to use cancellation mechanism to cancel an already running operation before rerunning it. – Medium
  3. Implement job queue. Basically every time you trigger an operation, a job gets added to a FIFO queue that processes each job and run to completion. – Advanced

Further Insights

Asynchronous code doesn’t mean the code will run faster, async/await is used to improve scalability, responsiveness, readability, and maintainability. Also async/await doesn’t mean new threads will be created. Thread creation is expensive. Also async/await tries to stay at the current thread as much as possible in order to avoid context switch unless specified with ConfigureAwait. Context switching incurs overheads; async/await captures the current context, goes through thread transition, and the state machine built in order to run your code. During context switching no work is happening on your code, it is needed to accommodate code execution by the framework. In other words, executing small piece of code asynchronously most likely will be slower than synchronous execution e.g. performance most likely to suffer from running a small operation asynchronously in a high frequency. What is a small operation? When Microsoft started adding async methods implementations to their API (Async suffix e.g. DbCommand.ExecuteReaderAsync), they chose to do it for methods that  take longer than 50 msec; primarily IO bound operations. Your best bet is to try and measure it using a profiler and decide accordingly.

For more insights on async/await check Async/Await FAQ.

Getting Started

Before adopting asynchronous programming in your code base, it is worth mentioning few things. As we said earlier, you will benefit from async/await when implemented from top to bottom. So, try to pick a vertical thin slice of your application to implement asynchronous programming. In a DDD layered bounded context it means four layers need to use asynchronous code; Presentation –> Application –> Business –> DataAccess. If, for instance your DataAccess doesn’t support asynchronous programming then probably it is not worth the hassle. Entity Framework supports asynchronous programming, but imagine your DAL is direct call to a database other than MSSQL, and that database has no async support in their API e.g. Oracle Data Provider for .NET (it is true at the time of writing).

What about your existing synchronous private and public API? Would you expose asynchronous wrappers for synchronous methods? The quick answer is NO and ideally you have to write a new asynchronous API method, which means more code to maintain. The longer answer is: it depends. If a method is on your public API then is best to be honest with the consumer and them decide what to do, possibly they can offload it using Task.Run(). However, for internal services some times you can wrap a synchronous method inside Task.Run() to achieve asynchronous call. Keep in mind this is OK for UI applications to achieve responsiveness, but due to the extra resources needed will worsen the scalability in a web application.

 

Leave a Reply

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