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

Insides Of Async / Await

Rate me:
Please Sign up or sign in to vote.
4.02/5 (28 votes)
28 Sep 2013CPOL5 min read 56.2K   45   16
It provides an understanding of async / await threading model with details about what problem it solves and how it is internally implemented.

Introduction

In this article, you will learn about async / await threading model, what problem it solves and how internally it is implemented. This article assumes that you are already familiar with multi-threaded programming with various different synchronization models. You may learn more about threads and synchronization in my previous article.

This article will take you through a problem statement and a solution using a traditional approach. Then it will solve the same problem using the async / await approach. Towards the end, it will discuss how internally it is implemented.

Problem Statement

You are working at a Windows Form application. In one of the forms, on a click of button, you are calling a method that returns a numeric value which you display on a text box. This method takes an average 10~20 secs for completion. Your client has complained that during this period, the application remains unresponsive. Some of the users have even restarted the application with the assumption that it just hanged. Following is the related code:

C#
public partial class NumericalAnalysisForm : Form
    {
        private void btnStartAnalysis_Click(object sender, EventArgs e)
        {
           txtAnalysisResult.Text = "Please wait while analysing the population.";
           txtAnalysisResult.Text = new AnalysisEngine().AnalyzePopulation().ToString();
        }
    }

    class AnalysisEngine
    {
        public int AnalyzePopulation()
        {
            //Sleep is used to simulate the time consuming activity.
            Thread.Sleep(4000);
            return new Random().Next(1, 5000);
        }
    }

Using Background Thread

After analyzing the code, you realized that since the main thread is performing all the calculations, it is not getting the opportunity to keep the application responsive. For the same reason, even the text "Please wait while analyzing the population." is not appearing on the screen. Hence you decided to refactor the code so that it is performing all the calculation on a background thread. Once the calculation is finished, you display the result as required. Following is the refactored code:

C#
public partial class NumericalAnalysisForm : Form
{
    private void btnStartAnalysis_Click(object sender, EventArgs e)
    {
        int result = 0;
        txtAnalysisResult.Text = "Please wait while analysing the population.";
        var analysisTask = Task.Run(() =>
            {

                result = new AnalysisEngine().AnalyzePopulation();
            });

        analysisTask.Wait();
        txtAnalysisResult.Text = result.ToString();
    }
}

class AnalysisEngine
{
    public int AnalyzePopulation()
    {
        //Sleep is used to simulate the time consuming activity.
        Thread.Sleep(10000);
        return new Random().Next(1, 5000);
    }
}

During the test, you found that application is still not responsive as required even though calculation is happening on a background thread. The reason is quite obvious. The main thread is in a blocked state (waiting for the task to be completed) and not getting the opportunity to keep the application responsive. Hence you further refactored the code to make sure the main thread is not in a blocked state while background thread is calculating. Here is the refactored code:

C#
public partial class NumericalAnalysisForm : Form
{
    private void btnStartAnalysis_Click(object sender, EventArgs e)
    {
        txtAnalysisResult.Text = "Please wait while analysing the population.";
        var analysisTask = Task.Run(() =>
            {
                txtAnalysisResult.Text = new AnalysisEngine().
                AnalyzePopulation().ToString();
            });
    }
}

class AnalysisEngine
{
    public int AnalyzePopulation()
    {
        //Sleep is used to simulate the time consuming activity.
        Thread.Sleep(10000);
        return new Random().Next(1, 5000);
    }
}

When you run the above application, it remains responsive during the calculation period. However just before displaying the result, an exception "Cross-thread operation not valid: Control 'txtAnalysisResult' accessed from a thread other than the thread it was created on." is thrown. This exception is thrown because you are accessing a UI control on a background thread. To solve this, you refactored the code as follows:

C#
public partial class NumericalAnalysisForm : Form
{
    public NumericalAnalysisForm()
    {
        InitializeComponent();
    }

    private void btnStartAnalysis_Click(object sender, EventArgs e)
    {
        txtAnalysisResult.Text = "Please wait while analysing the population.";
       Task.Run(() =>
            {
                int result = new AnalysisEngine().AnalyzePopulation();
                this.BeginInvoke(new MethodInvoker (()=>
                    {
                        txtAnalysisResult.Text = result.ToString();
                    }));
            });
    }
}

class AnalysisEngine
{
    public int AnalyzePopulation()
    {
        //Sleep is used to simulate the time consuming activity.
        Thread.Sleep(10000);
        return new Random().Next(1, 5000);
    }
}

With this refactored piece of code, everything looks ok. The application remains responsive during the calculation and the result is displayed properly on a text box. During the review process, the reviewer highlighted that AnalyzePopulation() method is being called from several areas of the codebase. To ensure that the application remains responsive in all those areas, the above piece of code needs to be duplicated. During the discussion with him, you agreed that AnalysisEngine class should be refactored so it exposes an asynchronous version of AnalyzePopulation() method which can be used in all the places to minimize duplication of code. After the review, you refactored the code as follows:

C#
public partial class NumericalAnalysisForm : Form
{
    private void btnStartAnalysis_Click(object sender, EventArgs e)
    {
        txtAnalysisResult.Text = "Please wait while analysing the population.";
        new AnalysisEngine().AnalyzePopulationAsync((result, exception) =>
            {
                txtAnalysisResult.Text = exception != null ?
                exception.Message : result.ToString();
            });
    }
}

class AnalysisEngine
{

    public void AnalyzePopulationAsync(Action<int, /> callBack)
    {
        var context = SynchronizationContext.Current ?? new SynchronizationContext();
        Task.Run(() =>
            {

                try
                {
                    int result = AnalyzePopulation();
                    context.Send((ignore) => callBack(result, null), null);
                }
                catch (Exception exp)
                {
                    context.Send((ignore) => callBack(int.MinValue, exp), null);
                }
            });
    }

    public int AnalyzePopulation()
    {
        //Sleep is used to simulate the time consuming activity.
        Thread.Sleep(10000);
        return new Random().Next(1, 5000);
    }
}

In this version of refactoring, you have exposed a generic asynchronous version of AnalyzePopulation() method which gives a call back to caller with result and exception. The callback happened in the same synchronization context as provided by the caller. In this case example, call back will happen on the GUI thread. If there is no synchronization context provided by the caller, a blank synchronization is created. E.g. If AnalyzePopulationAsync method is called by the console client, then call back will happen in the same background thread which executed the computation. If you have ever worked on a typical UI application, chances are that you must have encountered a similar problem and must have resolved nearly in a similar fashion.

Using Async / Await

With .NET 4.5, Microsoft has extended the language and BCL to provide a much simple and cleaner approach to resolve such issues. Following is the refactored code using async / await keywords.

C#
public partial class NumericalAnalysisForm : Form
{
    public NumericalAnalysisForm()
    {
        InitializeComponent();
    }

    private async void btnStartAnalysis_Click(object sender, EventArgs e)
    {
        txtAnalysisResult.Text = "Please wait while analysing the population.";

        try
        {
            int result = await new AnalysisEngine().AnalyzePopulationAsync();
            txtAnalysisResult.Text = result.ToString();
        }
        catch (System.Exception exp)
        {
            txtAnalysisResult.Text = exp.Message + Thread.CurrentThread.Name;
        }
    }
}

class AnalysisEngine
{
    public Task<int /> AnalyzePopulationAsync()
    {
        Task<int /> task = new Task<int />(AnalyzePopulation);
        task.Start();

        return task;
    }

    public int AnalyzePopulation()
    {
        //Sleep is used to simulate the time consuming activity.
        Thread.Sleep(10000);
        return new Random().Next(1, 5000);
    }
}

With the above code, we achieve the same results: the application remains responsive and the result is displayed properly. Here are the details how we managed to get the same results:

  • We refactored the AnalysisEngine class so AnalyzePopulationAsync() method is returning a task. The newly created task is executing the AnalyzePopulation() method in a background thread.
  • We added "async" keyword in btnStartAnalysis_Click method signature. It is an indication to the compiler so that the compiler can refactor the code during compilation.
  • Within btnStartAnalysis_Click method, we are calling the AnalyzePopulationAsync() method with await keyword. This indicates to the compiler that:
    • The current thread should immediately return from here
    • Whatever statements are after this point needs to be executed once the task returned by AnalyzePopulationAsync complete and needs to be executed in the current synchronization context.

With these indications, the compiler has enough information that it can refactor btnStartAnalysis_Click method during compilation so that the main thread will not be blocked and once task completes, the result will be displayed using the main thread. The interesting part is that code generated by compiler will nearly be same as what you refactored earlier. The only difference is that this time compiler is doing it for you and your code will look more clean and concise.

Few Things to be Noted

  • Like your implementation, the compiler generated code will execute the statements after await statement in the same task thread if there is no synchronization context.
  • If exception occurred in task, it will be thrown immediately after await statement in the same synchronization context.
  • Async/Await is just an indication to the compiler so it can refactor the code. It has no significance in the runtime.

Summary

Async is an important new feature in the .NET Framework. The async and await keywords enable you to provide significant user experience improvements in your applications without much effort on your part. In addition, Microsoft has added a large set of new async APIs within the .NET Framework that returns task, e.g., HTTPClient class and StreamReader class now exposes asynchronous APIs that can be used with async/await to keep the application responsive.

License

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


Written By
Software Developer (Senior) RBS Bank
United Kingdom United Kingdom
Over 14 years of experience in enterprise / real-time software development using Microsoft Technologies.


More details in LinkedIn Or Blog

Comments and Discussions

 
QuestionTypo? Action<int, Exception> Pin
leiyangge2-Jul-15 2:25
leiyangge2-Jul-15 2:25 
GeneralMy vote of 5 Pin
Rahul8322-May-15 21:39
Rahul8322-May-15 21:39 
Questionhow to use readlineasync in streamreader Pin
bobmarley768-Jul-14 3:04
bobmarley768-Jul-14 3:04 
GeneralMy vote of 3 Pin
Seyed Hamed Khatami2-Jul-14 1:59
Seyed Hamed Khatami2-Jul-14 1:59 
Questionthank you Pin
shiva guduri23-Apr-14 0:43
shiva guduri23-Apr-14 0:43 
Question[My vote of 2] not great Pin
BillW3310-Oct-13 11:03
professionalBillW3310-Oct-13 11:03 
GeneralMy vote of 2 Pin
Athari6-Oct-13 21:37
Athari6-Oct-13 21:37 
GeneralMy vote of 2 Pin
Paulo Zemek5-Oct-13 12:20
mvaPaulo Zemek5-Oct-13 12:20 
Question[My vote of 1] Where's the 'Insides of Async' part? PinPopular
FatCatProgrammer2-Oct-13 4:46
FatCatProgrammer2-Oct-13 4:46 
QuestionLines executed after an await statement Pin
Simon Gulliver1-Oct-13 3:12
professionalSimon Gulliver1-Oct-13 3:12 
AnswerRe: Lines executed after an await statement Pin
Tariq A Karim1-Oct-13 3:38
Tariq A Karim1-Oct-13 3:38 
News.Net Quiz Pin
Tariq A Karim30-Sep-13 6:48
Tariq A Karim30-Sep-13 6:48 
GeneralMy Vote of 5 Pin
maheshnakka30-Sep-13 4:02
professionalmaheshnakka30-Sep-13 4:02 
QuestionCheck generics signatures in the code Pin
ilog.km30-Sep-13 1:00
ilog.km30-Sep-13 1:00 
AnswerRe: Check generics signatures in the code Pin
Tariq A Karim30-Sep-13 6:53
Tariq A Karim30-Sep-13 6:53 
GeneralRe: Check generics signatures in the code Pin
ilog.km31-Oct-13 2:41
ilog.km31-Oct-13 2:41 

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.