Click here to Skip to main content
15,880,891 members
Articles / Programming Languages / C# 5.0

C# Async and Await Programming Model from Scratch

Rate me:
Please Sign up or sign in to vote.
4.82/5 (9 votes)
6 Mar 2016CPOL6 min read 40.3K   26   14
Explaining the async await programming model in C#

Introduction

This is a brief introduction to async and await keywords to a normal developer who wants to understand the basics and little insights to internals of how stuff works.

Background

Asynchronous programming is now an essential thing 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 struck until the operation is done.

Using async mechanism, we can just trigger long running operations and can do other tasks. Those long running operations do the job in a 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 to our code, it's our main thread which deals with user interface or the thread which primarily processes a web request or service UI. Sometimes, we ourselves write these kind of long running operations.

What is async and await

In simple sense, these are 2 new keywords introduced in .NET 4.5 to easily write asynchronous programs. They work in the method level. Of course, we cannot make classes 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, is it good to have language depend on a library written using the same language?

We will try to find out the answer to these questions in this article.

History of .NET async Programming

Threads were there from the very beginning of the .NET framework. They were the wrappers on operating system threads and little difficult to work with. Then more concepts such as background worker, async delegate and Task Parallel Library came to ease the async programming model. Those came as part of class library. C# language as such doesn't have 'out of the box' support for async programming until the async and await keywords are introduced with C# 4.0. Let's see how the async and await helps us in async programming by examining each of these methods.

Example

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 in easily. But that won't help us to learn the language features.

Synchronous Code

C#
public void Main()
{
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorial(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}
private void WriteFactorial(int no)
{
    int result = FindFactorialWithSimulatedDelay(no);
    Console.WriteLine("Factorial of {0} is {1}", no, result);
}
private static int FindFactorialWithSimulatedDelay(int no)
{
    int result = 1;
    for (int i = 1; i <= no; i++)
    {
         Thread.Sleep(500);
         result = result * i;
    }
    return result;
}

We can see there is a loop that runs from 1 to 5 using counter variable. It finds 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. This method here in the sample is going to put delay to simulate real life workload. In other sense, this is the long running operation.

Easily, we can see that the execution is happening in sequence. The WriteFactorial() call in loop waits until the factorial is calculated. Why should we wait here? Why can't we move to the next number as there is no dependency between numbers? We can. But what about the Console.WriteLine statement in WriteFactorial(). It should 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 the threading is difficult and needs more knowledge than a common developer, we are using async delegates mechanism. Below is the rewrite of WriteFactorial() method using async delegate.

Making It async Using Async Delegates

One of the easier methods used earlier was to use Asynchronous Delegate Invocation. It 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's completed. The below code explains it well which uses Func delegate.

C#
private void WriteFactorialAsyncUsingDelegate(int facno)
{
    Func<int, int> findFact = FindFactorialWithSimulatedDelay;
    findFact.BeginInvoke(facno, 
                        (iAsyncresult) =>
                                {
                                    AsyncResult asyncResult = iAsyncresult as AsyncResult;
                                    Func<int, int> del = asyncResult.AsyncDelegate as Func<int, int>;
                                    int factorial = del.EndInvoke(asyncResult);
                                    Console.WriteLine("Factorial of {0} is {1}", facno, factorial);
                                }, 
                        null);
}

public void Main()
{
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorialAsyncUsingDelegate(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}

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.

Drawbacks

We don't have direct option to cancel the task. Also, if we want to wait for one or more methods, it's a little 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, It looks 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. Can cancel task easily, etc... There is more to it. Below is a rewrite of our Factorial writing code with TPL.

C#
private void WriteFactorialAsyncUsingTask(int no)
{
    Task<int> task=Task.Run<int>(() =>
    {
        int result = FindFactorialWithSimulatedDelay(no);
        return result;
    });
    task.ContinueWith(new Action<Task<int>>((input) =>
    {
        Console.WriteLine("Factorial of {0} is {1}", no, input.Result);
    }));
}
C#
public void Main()
{
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorialAsyncUsingTask(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}

Here, we can see that the first task is run, then it's continuing with the next task which is the completed handler which receives notification of first task and writing the result to console.

Drawbacks

Still this is not a language feature. We need to refer to the TPL libraries to get support. The 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 is going to do operations in async manner and it may contain await keywords. Without async, we cannot await.

Then, we are awaiting on the factorial finding function. The moment the await is encountered during the execution, thread goes to the calling method and resumes execution from there. Here in our case, the counter loop, and takes the next number. The awaited code is executed using TPL as its task. As normal, it takes a thread from the pool and executes it. Once the execution is completed, the statements below the await will be executed.

Here also, we are not going to change anything in the FindFactorialWithSimulatedDelay().

C#
private async Task WriteFactorialAsyncUsingAwait(int facno)
{
    int result = await Task.Run(()=> FindFactorialWithSimulatedDelay(facno));
    Console.WriteLine("Factorial of {0} is {1}", facno, result);
}
public void Main()
{
    for (int counter = 1; counter < 5; counter++)
    {
        if (counter % 3 == 0)
        {
            WriteFactorialAsyncUsingAwait(counter);
        }
        else
        {
            Console.WriteLine(counter);
        }
    }
    Console.ReadLine();
}

This avoids the needs 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. More clearly, we can say async and await are syntactic sugar in C# language. Still not clear? In other sense, the .NET runtime doesn't know about async and await keywords.

Look at the disassembled code of WriteFactorialAsyncUsingAwait(). You may use reflector or similar tools to disassemble the assembly.

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.

Points of Interest

The interesting things are whether a language should depend upon a library/class created with it? Should the language know the parallel programming constructs? And finally, should the compiler modify our code which will give some new things when we debug our code?

History

  • 6th March, 2016: Initial publication

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect Orion Systems Integrators Inc
United States United States
Joy is a passionate software engineer with 10+ years of experience, currently working as an Architect at Orion Systems Integrators Inc. He specializes mainly in Microsoft technologies such as WPF,WCF, ASP.Net, Azure, Surface,SQL Server as well as other technologies like HTML5, AngularJS and NodeJS. Favorite languages are C#,JavaScript, TypeScript,Go,VB.Net & PowerShell. Along with his day to day technology topics, he is interested in anything related to software development such as hardware, security, artificial intelligence etc. He enjoys sharing his knowledge with others. More info about him can be found on his web site http://joymononline.in/ and technical blog http://joymonscode.blogspot.com

Comments and Discussions

 
GeneralMy vote of 5 Pin
jrobb2297-Mar-16 10:18
jrobb2297-Mar-16 10:18 
GeneralBad example Pin
Klaus Luedenscheidt6-Mar-16 19:55
Klaus Luedenscheidt6-Mar-16 19:55 
GeneralRe: Bad example Pin
Giulio_20106-Mar-16 20:41
Giulio_20106-Mar-16 20:41 
GeneralRe: Bad example Pin
Andrey Dryazgov7-Mar-16 5:22
professionalAndrey Dryazgov7-Mar-16 5:22 
GeneralRe: Bad example Pin
Giulio_20107-Mar-16 10:34
Giulio_20107-Mar-16 10:34 
GeneralRe: Bad example Pin
Andrey Dryazgov7-Mar-16 12:11
professionalAndrey Dryazgov7-Mar-16 12:11 
GeneralRe: Bad example Pin
Joy George K10-Mar-16 16:36
professionalJoy George K10-Mar-16 16:36 
GeneralRe: Bad example Pin
Andrey Dryazgov11-Mar-16 2:17
professionalAndrey Dryazgov11-Mar-16 2:17 
GeneralRe: Bad example Pin
DaveBlack10-Mar-16 15:24
DaveBlack10-Mar-16 15:24 
GeneralRe: Bad example Pin
Andrey Dryazgov10-Mar-16 15:44
professionalAndrey Dryazgov10-Mar-16 15:44 
GeneralRe: Bad example Pin
Joy George K10-Mar-16 16:32
professionalJoy George K10-Mar-16 16:32 
GeneralRe: Bad example Pin
Joy George K10-Mar-16 16:48
professionalJoy George K10-Mar-16 16:48 
GeneralRe: Bad example Pin
DaveBlack10-Mar-16 17:58
DaveBlack10-Mar-16 17:58 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.