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

Understanding await and async through code

Rate me:
Please Sign up or sign in to vote.
4.51/5 (23 votes)
9 Aug 2014CPOL3 min read 54.3K   40   12
The article provides a code first approach to understanding await/async and answers some of the basic questions.

Introduction

There are a lot of articles out there that explain the purpose of Await/Async and how it helps simplifying asynchronous programming. However I was always confused with the following:

  • Do async/await keywords create separate threads?
  • How can await suspend the current method (is the calling thread blocked)?
  • How does execution return to the calling method (if the calling thread is blocked)?

The point of this article is to answer these questions in a simple, easy to understand way. This article is meant for developers who are trying to understand asynchronous programming using await/async. 

Background

Async and Await keywords were added in .Net 4.5 & C# 5.0 as means to simplify asynchronous programming. The aim was to make it easier for developers to write asynchronous code and move the repetitive tasks to the compiler.

The earlier asynchronous programming model (APM) was based on IAsyncResult where asynchronous operations required Begin and End methods. For example the FileStream class implements the BeginRead and EndRead methods.

public override IAsyncResult BeginRead(byte[] array,int offset,int numBytes,AsyncCallback userCallback,   Object stateObject)

public override int EndRead(IAsyncResult asyncResult)

The new asynchronous programming model is based on the Task type that uses a single operation to represent the start and completion of the asynchronous operation. The BeginRead and EndRead method have been replaced by a new ReadAsync method.

public Task<int> ReadAsync(byte [] buffer, int offset, int count);

The task based asynchronous pattern is known as TAP.

So now that we have set the base, it is time to discuss the use of await/async and how they can be used simplify task based asynchronous programming.

Code first

Rather than starting with an example and getting lost in its details, we will look directly at code that uses the await keyword.

C#
int sum = await AddAsync(x, y);

int multiply = sum * z;

return multiply;

The code above calls a method that returns the sum of two numbers. The add method is asynchronous in nature and returns a Task<int>. The signature of the method looks like

public async Task<int> AddAsync(int x, int y)

The await keyword, as you might have read, suspends the current method execution and returns the control to the caller. The keyword does not create any threads. So how does it do this?

From MSDN,

Quote:

An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method

To demonstrate, we can rewrite the original code without using await as

var t = AddAsync(x, y);

var t2= t.ContinueWith((task) =>

{

 int sum = t.Result;

 int multiply = sum * z;

 return multiply;

},TaskScheduler.FromCurrentSynchronizationContext());

return t2;

The await keyword does not create additional threads as it runs on the current synchronization context.

This does answer two important questions.

  • No additional threads are created.
  • The remaining expressions are registered to continue after the task completes.

The await keyword does most of the magic. The async keyword was mainly added to avoid backwards compatibility problems when using the await keyword. According to a blog from MSDN,

Quote:

Requiring "async" means that we can eliminate all backwards compatibility problems at once; any method that contains an await expression must be "new construction" code, not "old work" code, because "old work" code never had an async modifier.

http://blogs.msdn.com/b/ericlippert/archive/2010/11/11/whither-async.aspx

However the async keyword does have one trick up its sleeve.

public async Task<int> AddAndMultiplyAsync(int x, int y, int z)

{

    int sum = await AddAsync(x, y);

    int multiply = sum * z;

    return multiply;

}

Looking at the method above we see the return type is Task<int>, however we are returning an integer. The magic of course is in the async keyword that changes the return value automatically to Task<T> where T is the type we are returning. If you are playing around with async you might have seen this error pop up

error CS4016: Since this is an async method, the return expression must be of type 'int' rather than 'Task<int>'

This is because if you try to return a Task in an async method, the return type should be Task<Task<T>>.

public async Task<Task<int>> AddAndMultiplyAsync(int x, int y, int z)

{

    return AddAsync(x, y);

}

 

Summary

Does Async/Await create a separate thread?

No – async changes the return value automatically to Task, and allows us to use the await keyword. await registers the remaining method as a continuation and returns control to the caller of the async method.

How can Await suspend the current method?

When we use the await keyword it wraps the remainder of the method call in a Task.ContinueWith block.

How does execution return to the calling method?

As the current method call is wrapped in a Task.ContinueWith block, the await keyword returns the Task<T>

History

  1. 09-August-2014 - Draft version.
  2. 09-August-2014 - Added background

 

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)
Australia Australia
A seasoned software engineer with over 13 years of commercial experience in software design/development.

Comments and Discussions

 
QuestionAwait suspends the execution of the thread where it is used, Then how it will improve performance. Pin
Bhupesh Goyal13-Aug-17 18:55
professionalBhupesh Goyal13-Aug-17 18:55 
AnswerRe: Await suspends the execution of the thread where it is used, Then how it will improve performance. Pin
Michael_Davies13-Aug-17 20:17
Michael_Davies13-Aug-17 20:17 
AnswerRe: Await suspends the execution of the thread where it is used, Then how it will improve performance. Pin
Michael_Davies14-Aug-17 1:50
Michael_Davies14-Aug-17 1:50 
PraiseClear and concise... Pin
Livio Francescucci22-Mar-17 21:31
Livio Francescucci22-Mar-17 21:31 
GeneralMy vote of 5 Pin
Amir Dashti6-Nov-15 4:24
professionalAmir Dashti6-Nov-15 4:24 
GeneralHardly a tip, let alone an article Pin
Clifford Nelson22-Mar-15 14:53
Clifford Nelson22-Mar-15 14:53 
QuestionNice short explanations! Pin
Your Display Name Here17-Mar-15 13:19
Your Display Name Here17-Mar-15 13:19 
SuggestionNeeds emphasis Pin
ArchAngel12328-Oct-14 13:41
ArchAngel12328-Oct-14 13:41 
Nice description - you pointed out that the awaiting thread isn't blocked and returns to its caller but it should be especially emphasized - this is where I've seen programmers get horribly screwed up in thinking liberally

While in a linear world it might appear to you that your code execution stops and then picks up after the await completes - it does NOT. You have no guaranteed that you will even continue execution on the thread you started on - especially important for UI developers trying to make use of async/await

There are two very common mistakes that I've seen:

1. Calling async methods from an upper loop without thinking about the consequences:

Filestream myfilestream = GetMeAFileStream();

void Cycle(IEnumerable<string> stuff)
{
foreach (string s in stuff)
{
WriteAnnotated(s);
}
}

void WriteAnnotated(string x)
{
WriteThis(DateTime.Now.ToString() + " " + x);
}

async void WriteThis(string x)
{
await myfilestream.WriteAsync(x);
}

In this case, they thought they were doing something good by making the writing to the filestream asynchronous but then neglected to consider that behavior in the rest of their code path. The loop at the top will ultimately call the async write for the first string in the enumerable and then immediately come back without the write completing allowing the top loop to move to the next string and try to fire it down to the file stream for writing (possibly without the first one having completed). The developer should have insured they used asyncs and awaits at each step to insure proper continuation - in that case the caller of Cycle() would be given control until the write finished and then whole continuation chain could move on to the next step

2. UI developers using async/await to minimize impact to the UI thread

async/await are extremely useful in UI development - especially if you make your event handlers async capable - but you need to recognize that while you'll enter your event handler on the UI thread, once you "await" your continuation is not guaranteed to be on the UI thread - so if you're going to update UI elements after the code following the await continues, you will need to check the UI element's InvokeRequired property and if necessary post your update back to the UI thread for execution.

In some ways the old model (pre-async/await) was less confusing because you have no choice but understand what was happening and deal with it properly - async/await is great and streamlines this style of programming greatly but you still need to picture the three dimensional behavior to avoid creating obscure problems for yourself
GeneralRe: Needs emphasis Pin
DotNetCodebased8-Nov-14 23:54
DotNetCodebased8-Nov-14 23:54 
QuestionWhere is definition of AddAsync? Pin
User 246299114-Oct-14 16:13
professionalUser 246299114-Oct-14 16:13 
AnswerRe: Where is definition of AddAsync? Pin
Talal Tayyab17-Feb-15 13:06
Talal Tayyab17-Feb-15 13:06 
QuestionGood! Pin
Cheranga Hatangala11-Aug-14 13:54
Cheranga Hatangala11-Aug-14 13:54 

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.