C# async and await programming model from scratch
This is a brief introduction to async and await keywords and explains the basics and provides an idea how it works.
Asynchronous programming is essential when we develop any application because it avoids waiting in main thread on long running operations such as disk I/O, network operations database access, etc. In normal case, if our program needs something to be done from the results of these long operations, our code is stuck until the operation is done and we proceed from that point.
Using async mechanism, we can just trigger long running operations and continue with other tasks. These long running operations does the job in different thread and when they complete it, they notify our main code and our code can do the post actions from here. When we refer our code, we are talking about our main thread which deals with user interface or the thread that primarily process a web request. Sometimes we ourselves write these kind of long running operations.
What is async and await?
In simple words, 2 new keywords were introduced in .Net 4.5 to easily write asynchronous programs. They work in the method level. The classes cannot be made to work in parallel as they are not unit of execution.
Are these keywords known to CLR, the .Net run-time or a wrapper over TPL Task Parallel Library? If they are wrappers, the language depends on a library written using same language? We will find out the answer to these questions in this article.
History of .Net async programming
Threads existed from the very beginning of the .Net framework. They were the wrappers on operating system threads and little bit difficult to work with. Then more concepts such as background worker, async delegate and Task Parallel Library came to the scene that eased the async programming model. The concepts came as part of class library. C# language doesn’t have ‘out of the box’ support for async programming until the async and await keywords were introduced with C# 4.0. Let’s see how the async and await helps us in async programming by examining each of these methods.
Let’s take the below example of finding factorial of first N numbers if they are completely divisible by 3. We are using console application for simplicity. If we had used a windows application we could easily hook into async event delegate handler and demo the async features easily. But that won’t help us to learn the language features.
We can see there is a loop run from 1 to 5 using counter variable. It finds out whether the current counter value is completely divisible by 3. If so, it writes the factorial. The writing function calculates the factorial by calling FindFactorialWithSimulatedDelay() method. The method in the sample is going to put delay to simulate real life workload. In other words, this is the long running operation.
The execution happens in sequence. The WriteFactorial() call in loop waits until the factorial is calculated. Why should we wait instead of moving to the next number as there is no dependency between numbers? We can but the Console.WriteLine statement in WriteFactorial() has to wait until the factorial is found. It means we can asynchronously call FindFactorialWithSimulatedDelay(), provided there is a call back to the WriteFactorial(). When the async invocation happens, the loop can advance counter to next number and call the WriteFactorial().
Threading is one way we can achieve it. Since threading is difficult and needs more knowledge than a developer would be equipped with, we use async delegates mechanism. Below is the rewrite of WriteFactorial() method using async delegate.
Making it async using async delegates
One of the easier method used earlier was Asynchronous Delegate Invocation which uses the Begin/End method call mechanism. Here, the run-time uses a thread from thread pool to execute the code and we can have call backs once it is completed. Given below is a code that explains it using Func delegate.
There is no change in finding factorial. We simply added new function called WriteFactorialAsyncUsingDelegate() and modified the Main to call this method from the loop.
As soon as the BeginInvoke on findFact delegate is called, the main thread goes back to the counter loop, then it increments the counter and continues looping. When the factorial is available the anonymous call back will hit and it will be written into console.
We don’t have direct option to cancel the task. Also, if we want to wait for one or more methods it is a bit difficult.
Also, we can see that the piece of code is not wrapped as object and we need to battle with the IAsyncResult object to get the result back. TPL solves that problem too, as it is more object oriented. Let’s have a look.
Improving async programming using TPL
TPL is introduced in .Net 4.0. We can wrap the asynchronous code in a Task object and execute it. We can wait on one or many tasks to be completed such as easily cancel task. Below is a rewrite of our Factorial writing code with TPL.
The first task is run then continues with the next task of the completed handler that receives notification of first task and writing the result to console.
Since this is not a language feature, we need to refer the TPL libraries to get the support. Main problem here is the effort to write the completed event handler. Let’s see how this can be rewritten using async and await keywords.
The language feature async and await
We are going to see how the TPL sample can be rewritten using async and await keywords. We decorated the WriteFactorialAsyncUsingAwait method using async keyword to denote this function which is going to do operations in an async manner and it may contain await keywords. Without async, we cannot await.
Then we are awaiting on the factorial finding function. The moment await is encountered during execution, a thread goes to the calling method and resumes execution from there. In this case, the counter loop takes next number. The awaited code is executed using TPL as its task. Normally it takes a thread from the pool and executes it. Once the execution is completed, the statements below the await will be executed. Also, we are not going to change anything in the FindFactorialWithSimulatedDelay().
This avoids the need for extra call back handlers and developers can write the code in a sequential manner.
What is the relation with Task Parallel Library and async await keywords
The keywords async and await make use of TPL internally. In other words, async and await are syntactic sugar in C# language. The .Net runtime doesn’t know about async and await keywords.
Should a language depend on a library/class created with it?
This is an age old question. In C or C++, the language was always independent and the libraries were fully dependent on it. But if we look from introduction of yield keyword in C#, we can see there is a marriage between language features (keywords) and libraries. Here, yield which is a language feature, depends on IEnumerable interface created using the language itself. Then, the compiler does the magic and replaces the yield keyword with corresponding IEnumerable implementation making sure CLR doesn’t know about yield keyword.
Another example is using keyword. It is tightly coupled with IDisposable interface. Since then, many syntactic sugars has been added. More details can be found in below link.
Personally, I don’t prefer mixing language features with libraries. Let language evolve its own and libraries depend on language. If we do the other way the compiler is forced to inject more code and we know adding more lines is not coming in free. But unfortunately we are in a world where coding, and not execution of the code, needs to be fast.
Should the compiler modify our code?
The main problem with compiler modifying our code is debugging. There are chances that we will see call stack of our application which contains symbols not written by us. Try to see the call stack by raising an exception in an anonymous method. If there are many anonymous methods in the application, that’s it. We are done in debugging.
Should the language know parallel programming and threading?
Since the threading is managed by OS, should the language care about threading? Should the threading be a library or integrated language feature?
Nowadays most of the hardware has multiple cores and if the language doesn’t provide integrated features, nobody will leverage multiple cores. This is because either development community is wary of threading or it requires additional coding time. If the language provides an easy way, developers can focus more on the functionality or business side of the app than threading which is infrastructure related.
So I really want my language and associated runtime know parallel and async programming. However, please try to avoid tight coupling with class library and stop compiler altering my code.
When should we use it?
We can use async and await anytime we are waiting for something, i.e. whenever we are dealing with async scenarios. Examples are file IO, network operations, database operations etc. This will help us to make our UI responsive. So go ahead and make sure your APIs are await-able.
This article has been re-published from author’s personal blog. For reference – Click Here
Joy George Kunjikkuru
Joy hold a MBA degree from Bharatiar University, Coimbatore and a B.Tech degree in Computer Science from Anna University, Chennai.
Subscribe to our Blog
Get updates on new articles, webinars, whitepapers and other resources.