Insides of Async PDF

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 4

Insides Of Async / Await

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:
o The current thread should immediately return from here
o 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.

You might also like