Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

await statement vs. expression

Tags:

c#

async-await

I'm following the await tutorial on the MSDN, and I'm trying to figure out the difference between using await as a statement versus using await as an expression. This whole async-await thing is bending my mind and I can't find any examples for this particular case.

Basically, I wanted to see how to use multiple awaits asynchronously, meaning I don't want to have to wait for the first one to complete before the second one begins. This, to me, defeats the purpose of asynchrony to begin with:

private async void button1_Click(object sender, EventArgs e)
{
    // Using await as an expression
    string result_a = await WaitAsynchronouslyAsync();
    string result_b = await WaitAsynchronouslyAsync();

    // This takes six seconds to appear
    textBox1.Text = result_a + Environment.NewLine;
    textBox1.Text += result_b;
}

public async Task<string> WaitAsynchronouslyAsync()
{
    await Task.Delay(3000);
    return "Finished";
}

However, with a subtle change it only takes 3 seconds total for the two "Finished"s to appear, which is what I'd want -- the two awaits running truly asynchronously:

private async void button1_Click(object sender, EventArgs e)
{
    var a = WaitAsynchronouslyAsync();
    var b = WaitAsynchronouslyAsync();

    // Using await as a statement
    await a;
    await b;

    // This takes three seconds to appear
    textBox1.Text = a.Result + Environment.NewLine;
    textBox1.Text += b.Result;
}

My question is, why do these behave differently? What subtle point am I missing here?

like image 457
Jeff Avatar asked Sep 19 '12 21:09

Jeff


People also ask

What is await expression?

Description. The await expression causes async function execution to pause until a promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment. When resumed, the value of the await expression is that of the fulfilled promise.

What exactly does await do?

An await is an asynchronous wait. It is not a blocking call and allows the caller of your method to continue. The remainder of the code inside the method after an await will be executed when the Task returned has completed. In the first version of your code, you allow callers to continue.

Should I use await or result?

Answers. Always follow the standard async/await programming pattern from top down. Do not use . Result() as it is only used to invoke an async method from synchronous code.

What is the purpose of await in C#?

The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes. When the asynchronous operation completes, the await operator returns the result of the operation, if any.


2 Answers

Firstly, you need to distinguish between parallelism and asynchrony. In the first case, it could easily still be worth performing the operations synchronously (and indeed the second operation may depend on the results of the first) in order to free up the UI thread etc.

But as for why they behave differently - await is only an expression. It's the kind of expression which can appear as a statement, but it will behave the same way, just like calling a method which returns a string, but ignoring the return value. You can see that by changing your first code to:

// Still takes 6 seconds...
var a = WaitAsynchronouslyAsync();
await a;

var b = WaitAsynchronouslyAsync();
await b;

That will still take 6 seconds. The point is that you're only starting the second asynchronous operation after you've waited for the first one to finish. In your second example, both asynchronous operations occur at the same time.

You can still do that and assign the value to a variable, you just need to remember the awaitables:

// This will only take 3 seconds
var a = WaitAsynchronouslyAsync();
var b = WaitAsynchronouslyAsync();
string result_a = await a;
string result_b = await b;

So basically, the difference isn't to do with statement/expression - it's to do with whether the sequence is start/await/start/await or start/start/await/await.

like image 104
Jon Skeet Avatar answered Sep 24 '22 18:09

Jon Skeet


In both situations the await keyword introduces asynchrony. The reason you're seeing the difference is that in case 1 you start both tasks sequentially, where in case 2 you let them run in parallel.

Maybe a step by step explanation of both situations clears things up

string result_a = await WaitAsynchronouslyAsync(); 
string result_b = await WaitAsynchronouslyAsync(); 

What happens here is:

  • a task (a) is started
  • task a is awaited (control is returned to the caller)
  • when task a is finished after 3 seconds, the method resumes; task (b) is started
  • task b is awaited (control is returned to the caller)
  • when task b is finished after another 3 seconds, the text appears

In the second case:

var a = WaitAsynchronouslyAsync();     
var b = WaitAsynchronouslyAsync();     
await a;     
await b;     
  • task (a) is started
  • task (b) is started
  • task (a) is 'awaited' (i.e., control is returned to the caller)
  • when task (a) is finished after 3 seconds, task (b) is awaited
  • since task b was started roughly 3 seconds ago, it's finished more or less at the same time as (a)
  • the text appears
like image 39
jeroenh Avatar answered Sep 24 '22 18:09

jeroenh