Click here to Skip to main content
Click here to Skip to main content

Insides Of Async / Await

, 28 Sep 2013
Rate this:
Please Sign up or sign in to vote.
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:

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:

    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:

    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:

    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:

    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.

    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)

Share

About the Author

Tariq A Karim
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
Follow on   LinkedIn

Comments and Discussions

 
Questionhow to use readlineasync in streamreader Pinmemberbobmarley768-Jul-14 3:04 
GeneralMy vote of 3 Pinmemberhamedkha2-Jul-14 1:59 
Questionthank you Pinmembershiva guduri23-Apr-14 0:43 
Question[My vote of 2] not great PinprofessionalCIDev10-Oct-13 11:03 
GeneralMy vote of 2 PinprofessionalAthari6-Oct-13 21:37 
GeneralMy vote of 2 PinprofessionalPaulo Zemek5-Oct-13 12:20 
Question[My vote of 1] Where's the 'Insides of Async' part? PinmemberFatCatProgrammer2-Oct-13 4:46 
QuestionLines executed after an await statement PinmemberSimon Gulliver1-Oct-13 3:12 
AnswerRe: Lines executed after an await statement [modified] PinmemberTariq A Karim1-Oct-13 3:38 
News.Net Quiz PinmemberTariq A Karim30-Sep-13 6:48 
GeneralMy Vote of 5 Pinprofessionalmaheshnakka30-Sep-13 4:02 
QuestionCheck generics signatures in the code Pinmemberilog.km30-Sep-13 1:00 
AnswerRe: Check generics signatures in the code PinmemberTariq A Karim30-Sep-13 6:53 
GeneralRe: Check generics signatures in the code Pinmemberilog.km31-Oct-13 2:41 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140827.1 | Last Updated 28 Sep 2013
Article Copyright 2013 by Tariq A Karim
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid