#TECH

Understanding Asynchronous Programming with Async and Await in C#

 

synchronous vs asynchronous

First of all, writing asynchronous code is hard without tooling as you’ll end up creating a maze of twisted little code, which is really hard to understand.

What is Asynchronous Programming ?

  • Asynchrony is not about threading, it is about the mode of execution.
  • Also, Asynchrony is not about making many threads to do something. Rather it is about making no thread to do something.
  • We have moved from the ancient Synchronous model i.e. while I’m waiting there is one thread running (Synchronous) to while I’m waiting there is no thread running (Asynchronous). So, we don’t have to care about the threads at all.

Don’t worry if this is not clear enough right now, I will explain all these as we go along this post.

Another important aspect of asynchronous programming that we must understand is that Asynchrony ! = Parallelism.  

Asynchrony can be taken as the way of removing threads. When you have concurrency it means that you want to do many things at the same time in your code; this does not necessarily mean that you need parallelism or multiple threads of execution. If you can give the work load to someone else and react when they are finished, you can achieve a great deal of concurrency with very little parallelism.

Where we can use this?

In an execution model where we are asking for something which is not going to or might not come back straight away or which is not even guaranteed to come back.

We are used to a programming model where we ask a question and we do nothing until we get the answer. But, it is not like that in the real world at all. We don’t make an online order and wait at our doors until it arrives, rather we engage ourselves with other work.

Now, let’s look under the hood of c# async and await and see how to use it.

I have seen at a lot many places in our code where we do…

Await something async
Await something async
Await something async..

Await is not just a method that returns a task of T. Basically, it is to start something and give me back a token. And, the token we use in .net is a task of T.

How to do asynchronous programming with Async and Await in C#?

Async keyword is applied to a method which informs the compiler that we want to use asynchronous features. This keyword does not change the method signature neither does it change how the method is used, it only changes how the method is compiled.

Applying an async keyword to a method doesn’t make it asynchronous. Async simply declares that we want to use await keyword but we are not bound to do so. Using await keyword makes a big difference in our code and how it looks and works after compilation.

Now, lets take an example code:

 class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Starting Task");

            var worker = new Worker();
            worker.DoWork();

            while (!worker.Iscomplete)
            {
                Console.WriteLine(".");
                Thread.Sleep(100);
            }
            Console.WriteLine("Done");
            Console.ReadKey();
        }
    }

    public class Worker
    {
        public bool Iscomplete { get; private set; }

        public void DoWork()
        {
            Iscomplete = false;
            Console.WriteLine("Doing Work");

            LongOperation();

            Console.WriteLine("Work Completed");

            Iscomplete = true;
        }

        public void  LongOperation()
        {
            Console.WriteLine("Working");
            Thread.Sleep(2000);
        }

    }

 

In this code, the worker class needs to do some expensive work which needs to get done while the user waits. When we run this, we will not see any progress indicators as our LongOperation() is synchronous, which means it will block until its done.

Now, let’s add Aysnc keyword to our DoWork method and see what happens.

add Aysnc keyword to our DoWork method

It will actually do the same thing it did without the async keyword but this async keyword had changed our code during compilation. Let’s look at the complied code. I have used ilspy  to decompile the assembly.

The async methods are converted into asynchronous code by building and using a state machine.

The async methods are converted into asynchronous code

 

// System.Runtime.CompilerServices.AsyncMethodBuilderCore
[DebuggerStepThrough, SecuritySafeCritical]
internal void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine
{
    if (stateMachine == null)
    {
        throw new ArgumentNullException("stateMachine");
    }
    Thread currentThread = Thread.CurrentThread;
    ExecutionContextSwitcher executionContextSwitcher = default(ExecutionContextSwitcher);
    RuntimeHelpers.PrepareConstrainedRegions();
    try
    {
        ExecutionContext.EstablishCopyOnWriteScope(currentThread, false, ref executionContextSwitcher);
        stateMachine.MoveNext();
    }
    finally
    {
        executionContextSwitcher.Undo(currentThread);
    }
}

 

If we go through the code, we can actually see the code that starts our state machine to do work by invoking the MoveNext method on our state machine.

If we look closer, we will see that the context switching is being taken care of by this method. And, we really don’t have to worry about threads when we are using async in c#.

Now, let’s apply await to the LongOperation() method and as we cannot await a void method, we need our long operation method to return a task.

apply await to the LongOperation() method

 

Now, let’s run this code and we’ll see that our wait with dots are working unit the work is complete. Now, we are running asynchronously.
running asynchronously

 

Let’s take a look at the entire code and see what has changed in our program.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
namespace Test
{
    public class Worker
    {
        [CompilerGenerated]
        [StructLayout(LayoutKind.Auto)]
        private struct <DoWork>d__0 : IAsyncStateMachine
        {
            public int <>1__state;
            public AsyncVoidMethodBuilder <>t__builder;
            public Worker <>4__this;
            void IAsyncStateMachine.MoveNext()
            {
                try
                {
                    int num = this.<>1__state;
                    if (num != -3)
                    {
                        this.<>4__this.Iscomplete = false;
                        Console.WriteLine("Doing Work");
                        Worker.LongOperation();
                        Console.WriteLine("Work Completed");
                        this.<>4__this.Iscomplete = true;
                    }
                }
                catch (Exception exception)
                {
                    this.<>1__state = -2;
                    this.<>t__builder.SetException(exception);
                    return;
                }
                this.<>1__state = -2;
                this.<>t__builder.SetResult();
            }
            [DebuggerHidden]
            void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine param0)
            {
                this.<>t__builder.SetStateMachine(param0);
            }
        }
        public bool Iscomplete
        {
            get;
            private set;
        }
        [DebuggerStepThrough, AsyncStateMachine(typeof(Worker.<DoWork>d__0))]
        public void DoWork()
        {
            Worker.<DoWork>d__0 <DoWork>d__;
            <DoWork>d__.<>4__this = this;
            <DoWork>d__.<>t__builder = AsyncVoidMethodBuilder.Create();
            <DoWork>d__.<>1__state = -1;
            AsyncVoidMethodBuilder <>t__builder = <DoWork>d__.<>t__builder;
            <>t__builder.Start<Worker.<DoWork>d__0>(ref <DoWork>d__);
        }
        private static void LongOperation()
        {
            Console.WriteLine("Working");
            Thread.Sleep(2000);
        }
    }
}

 

So, now we have seen what happens when we write async code. We must understand that Asynchronous methods using async and await in c# does not come for free, there is a cost associated with it, which should be considered before using it anywhere.
Just because you can, does not mean you should!

Update – 19 Aug ’15

As mentioned in this really good post by Stephen Cleary, Task.Factory.StartNew can be dangerous because of its default values and it is safer to use Task.Run.

You might also like