Reputation: 52107
I'm still learning the async
/await
, so please excuse me if I'm asking something obvious. Consider the following example:
class Program {
static void Main(string[] args) {
var result = FooAsync().Result;
Console.WriteLine(result);
}
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = await t2;
return result1 + result2;
}
static Task<int> Method1Async() {
return Task.Run(
() => {
Thread.Sleep(1000);
return 11;
}
);
}
static Task<int> Method2Async() {
return Task.Run(
() => {
Thread.Sleep(1000);
return 22;
}
);
}
}
This behaves as expected and prints "33" in the console.
If I replace the second await
with an explicit wait...
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = t2.Result;
return result1 + result2;
}
...I seem to get the same behavior.
Are these two examples completely equivalent?
And if they are equivalent in this case, are there any other cases where replacing the last await
by an explicit wait would make a difference?
Upvotes: 0
Views: 664
Reputation: 456437
They are not equivalent.
Task.Result
blocks until the result is available. As I explain on my blog, this can cause deadlocks if you have an async
context that requires exclusive access (e.g., a UI or ASP.NET app).
Also, Task.Result
will wrap any exceptions in AggregateException
, so error handling is harder if you synchronously block.
Upvotes: 2
Reputation: 52107
OK, I think I figured this out so let me sum it up, in what will hopefully be a more complete explanation than the answers provided so far...
Replacing the second await
with an explicit wait will have no appreciable effect on a console application, but will block the UI thread of a WPF or WinForms application for the duration of the wait.
Also, the exception handling is slightly different (as noted by Stephen Cleary).
In a nutshell, the await
does this:
await
) to the current synchronization context, if there is one. Essentially, await
is trying to return us where we started from.
The second (and third and so on...) await
does the same.
Since the console applications typically have no synchronization context, continuations will typically be handled by the thread pool, so there is no issue if we block within the continuation.
WinForms or WPF, on the other hand, have synchronization context implemented on top of their message loop. Therefore, await
executed on a UI thread will (eventually) execute its continuation on the UI thread as well. If we happen to block in the continuation, it will block the message loop and make the UI non-responsive until we unblock. OTOH, if we just await
, it will neatly post continuations to be eventually executed on the UI thread, without ever blocking the UI thread.
In the following WinForms form, containing one button and one label, using await
keeps the UI responsive at all times (note the async
in front of the click handler):
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e) {
var result = await FooAsync();
label1.Text = result.ToString();
}
static async Task<int> FooAsync() {
var t1 = Method1Async();
var t2 = Method2Async();
var result1 = await t1;
var result2 = await t2;
return result1 + result2;
}
static Task<int> Method1Async() {
return Task.Run(
() => {
Thread.Sleep(3000);
return 11;
}
);
}
static Task<int> Method2Async() {
return Task.Run(
() => {
Thread.Sleep(5000);
return 22;
}
);
}
}
If we replaced the second await
in FooAsync
with t2.Result
, it would continue to be responsive for about 3 seconds after the button click, and then freeze for about 2 seconds:
await
will politely wait its turn to be scheduled on the UI thread, which would happen after Method1Async()
task finishes, i.e. after about 3 seconds, t2.Result
will rudely block the UI thread until the Method2Async()
task finishes, about 2 seconds later.If we removed the async
in front of the button1_Click
and replaced its await
with FooAsync().Result
it would deadlock:
FooAsync()
task to finish, FooAsync().Result
.The article "Await, SynchronizationContext, and Console Apps" by Stephen Toub was invaluable to me in understanding this subject.
Upvotes: 0
Reputation: 13579
Your replacement version blocks the calling thread waiting for the task to finish. It's hard to see a visible difference in a console app like that since you're intentionally blocking in Main, but they're definitely not equivalent.
Upvotes: 2