Reputation: 31
It is working fine with the following code:
private void button1_Click(object sender, EventArgs e)
{
Task<int> t = test(5);
Console.WriteLine(t.Result);
}
private Task<int> test(int n)
{
return Task.Run(() =>
{
return n;
});
}
But if I wrap the test method with an async method, it doesn't work:
private Task<int> test(int n)
{
return Task.Run(() =>
{
return n;
});
}
public async Task<int> wrap()
{
return await test(5);
}
private void button1_Click(object sender, EventArgs e)
{
Task<int> t = wrap();
Console.WriteLine(t.Result);
}
The form loses response. If I use await, it works as expected.
Update1: These two answers are both correct, but I can only mark one as answer. Based on the understanding of this question, I did further test. I used ConfigureAwait in wrap method to leave the continuation to run in thread other than UI:
public async Task<int> wrap()
{
return await test(5).ConfigureAwait(false);
}
It works fine. And then I tested this:
public async Task<int> wrap()
{
int i = await test(5).ConfigureAwait(false);
int j = i + await test(3);
return j;
}
It's working on the first time when I clicked the button, but deadlock again on second click. If I added ConfigureAwait(false) after test(3), like this:
public async Task<int> wrap()
{
int i = await test(5).ConfigureAwait(false);
int j = i + await test(3).ConfigureAwait(false);
return j;
}
it's working again, but this doesn't make sense to me. Because of the first ConfigureAwait(false), all the following synchronization part in wrap() should be run on non-UI thread. I don't understand why the second ConfigureAwait(false) is necessary.
Update2:
private Task<int> test(int n)
{
return Task.Run(() =>
{
Console.WriteLine("test(" + n + "): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
return n;
});
}
public async Task<int> wrap()
{
Console.WriteLine("1.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
int i = await test(5).ConfigureAwait(false);
Console.WriteLine("2.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
int j = i + await test(3);
Console.WriteLine("3.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
return j;
}
private void button1_Click(object sender, EventArgs e)
{
try
{
Console.WriteLine("1.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
var t = wrap();
Console.WriteLine("2.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
Console.WriteLine(t.Result);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
After several click, the form is freezing, and the output is:
1.button1_Click(): 8
1.wrap(): 8
test(5): 13
2.wrap(): 8
2.button1_Click(): 8
test(3): 13
To my surprise, "2.wrap():" is running in the same thread with "1.wrap():", rather than "test(5)". It seems that the code after the ConfigureAwait(false) can also jump back to UI thread.
Upvotes: 3
Views: 362
Reputation: 29981
It's a deadlock -- two Task
s running in a single-threaded scheduler are waiting for each-other to complete.
Two very important things to understanding this:
await
comes back on the same scheduler as it started in.await
in UI calls is one which dispatches onto this event loop. Only a single Task can be executing at once in this scheduler.Because of these, you need to be especially careful about how you call blocking methods on a Task
which executes inside of the UI's scheduler.
In your second example, the call to Result
is waiting for the continuation setup in wrap
to complete. The continuation is scheduled to be run on the UI thread, which the call to Result
just happens to be blocking, so neither will ever complete.
Upvotes: 4
Reputation: 456507
You are causing a deadlock which I explain in detail on my blog. In summary, the await
keyword will (by default) capture the current context before the await
and resume the rest of the async
method within that context. In this case, that "context" is the UI context, which executes code on the UI thread. So, when your code calls Result
, it is blocking the UI thread, and when the await
completes, it can't execute the rest of wrap
on the UI thread. Thus, deadlock.
The best way to avoid this deadlock is to use await
instead of Result
or Wait
for asynchronous tasks. I.e., change your click method to:
private async void button1_Click(object sender, EventArgs e)
{
Task<int> t = wrap();
Console.WriteLine(await t);
}
Upvotes: 3