From Event-based to Task-based Asynchronous Programming in C#

Today we are going to review and rewrite the old way of writing asynchronous applications in C#. This is some hardcore deep technical content about Asynchronous Programming in C#, if you are new to these concepts, you can check this blog post which provides a very good introduction.

You can find the whole source code for this blog post here

Traditional async operations

Back in the early days of my general and high school, the only way you could write asynchronous code in C# 2.0 and Visual Studio 2005/2008 was by either implementing the Event-based Asynchronous Pattern (EAP) or Asynchronous Programming Model (APM) (both are now obsolute as of C# 4.0 – thank God 😌) both of which were very hard to understand. write and debug. I implemented the APM in this post where I talked about asynchronous delegates. The APM uses the IAsyncResult design pattern which is implemented as two methods named BeginOperationName and EndOperationName that begin and end the asynchronous operation OperationName respectively.

The flaws of the APM appear immediately:

  • in order to finish the thread you must call the EndOperationName method from within the callback function
  • no easy way to cancel the async operation because you have no control whats happening in the background once the BeginOperationName started (you can make use of a CancellationTokenSource which adds extra complexity to the code)
  • callbacks are not synchronized with the caller thread meaning that you will have to add extra complexity either by using the CompletedSynchronously property of the IAsyncResult or implementing your own synchronization algorithm
  • multiple callback synchronization is even harder as you would have to use WaitHandles

The EAP makes use of delegates and events and describes one way for classes to present asynchronous behavior. The events are raised on another thread, so we need a synchronization mechanism for this (check out the differences between a Thread and a Task here). Let’s review the code while showcasing its functionality.

var primeNumberCalculator = new Traditional.PrimeNumberCalculator();

primeNumberCalculator.CalculatePrimeCompleted += new Traditional.PrimeNumberCalculator.CalculatePrimeCompletedEventHandler(CalculatePrimeCompleted);
primeNumberCalculator.ProgressChanged += new Traditional.PrimeNumberCalculator.ProgressChangedEventHandler(ProgressChanged);

Besides instantiating the main entry point class, we need to create new instances of events and create delegate methods as seen below.

static void CalculatePrimeCompleted(object sender, CalculatePrimeCompletedEventArgs e)
    Console.WriteLine("[EAP] The number is {0}",
        e.IsPrime ? "prime" : "not prime");

static void ProgressChanged(ProgressChangedEventArgs e)
    // Do something when the progress changes

The ProgressChanged event fires whenever a prime number was found that is less than the number to test and after it is added to the primeNumberList.

primeNumberCalculator.CalculatePrimeAsync(1299827, Guid.NewGuid());

 * Thread synchronization done the easy way
 * We need to hang the current thread to wait for the
 * prime number calculator to finish processing

primeNumberCalculator.CalculatePrimeAsync(12345678, Guid.NewGuid());

 * Notice how for small numbers the wait is to high
 * and for big numbers the wait to low
 * In order to implement a better thread synchronization you will need
 * to write extra complexity which is not the purpose of this example

In order to have control over the newly created threads, we want to assign a Guid to every thread and perhaps keep track of each thread status via the Guid. In this specific example I use Thread.Sleep method to mimic thread synchronization (never use this kind of logic in production environments). I’ve used the Thread.Sleep approach because writting thread synchronization logic isn’t the topic of this blog post. In production enviorments, this logic can also be application-specific.own

Let’s talk code now. To make use of delegates and events we obviously need private delegate and event properties inside our class (first unnecessary added complexity). If we want to write as less code as possible, we would want to pass as much information as we can to the EventArgs objects which we custom build (second unnecessary added complexity). We also need notification mechanisms in order to proper fire up the events/delegates (third unnecessary added complexity). We also need thread resource locking using the lock statement (fourth unnecessary added complexity). All in all, the unnecessary added complexity adds up to approximately 200 lines of code and when you compare that to the entire project which has approximately 300-350 lines you figure that you wrote a lot of unnecessary code (more than a half).

Modern async operations

As of C# 4.0, you can write asynchronous code using the Task-based Asynchronous Pattern (TAP) which provides a more easy to implement and to debug code and presents the asynchronous operations into a single method and combines the status of the operation and the API used for interacting with those operations into a single object (the System.Threading.Tasks.Task and System.Threading.Tasks.Task<TResult> types in the System.Threading.Tasks namespace). As of .NET 4.5, most of the classes with support for APM also have Async methods (as a standard, every method that makes use of the TAP model is named OperationNameAsync) already implemented and you can wrap the APM into a TAP model using the TaskFactory.FromAsync or method. If you want to run an operation on another thread, you can use the Task.Run method by making use of lamba methods (coming soon 😎).

Let’s review the flaws of the APM and how the TAP model handles them:

  • cancellation support  by default, implemented in the Task class
  • automatic thread synchronization
  • easy multiple asynchronous operations coordination by making use of the Task.ContinueWith methods and its overloads.

Let’s review the code (the rewriting of the EAP example to TAP) while showcasing its functionality and seeing how it differs from the EAP model.

static async Task TAPExample()
    //var primeNumberCalculator = new Modern.PrimeNumberCalculator(displayProgress: false);
    var primeNumberCalculator = new Modern.PrimeNumberCalculator(displayProgress: true);

    Console.WriteLine("[TAP] The first number is {0}",
        await primeNumberCalculator.CalculatePrimeAsync(1299827) ? "prime" : "not prime");

    Console.WriteLine("[TAP] The second number is {0}",
        await primeNumberCalculator.CalculatePrimeAsync(1234567) ? "prime" : "not prime");

Notice how we only need to instantiate the main entry point class without the need for extra properties. As you may have observed in my previous post, the asynchronous code look rather synchronous, more readable, and the result can be stored in a variable, in the same method.

public Task<bool> CalculatePrimeAsync(int numberToTest)
    var firstDivisor = 1;

    var task = Task
        .ContinueWith((prevTask) => IsPrime(prevTask.Result, numberToTest, out firstDivisor));

    return task;

This is all the asynchronous logic you need to write, right here in this method. As mentioned above, we wrap synchronous code into the TAP model using the Task.FromResult method and we Task.ContinueWith passing the primeNumberList to the IsPrime method thus initiating the algorithm.

And that is all. Seriously, that is all the asynchronous code we had to write, that very method above, the rest in algorithm specific code. If we were to talk code, the whole application has aproximately 100 lines of code and the TAP asynchronous logic has aproximately 20 lines. If we look at the EAP application, we see a huge improvement in both word count and “reinvent the wheel” code.

In conclusion

As you may have observed, a bit of C# coding knowledge is required. For a better understanding of how powerful the C# language really is, you can check out this repository full with basic C# projects. If you want to go deeply into advanced C# topics, you can check out this repository. Stay tuned on this blog (and star the microsoft-dx organization) to emerge in the beautiful world of “there’s an app for that”.

Post image source:

Example adapted from:

Leave a Reply