使用以下代码正常工作:
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;
});
}
但是如果我使用异步方法包装测试方法,它就不起作用:
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);
}
表格失去回应。如果我使用await,它会按预期工作。
UPDATE1: 这两个答案都是正确的,但我只能将其标记为答案。基于对这个问题的理解,我做了进一步的测试。我在wrap方法中使用了ConfigureAwait 让延续在UI以外的线程中运行:
public async Task<int> wrap()
{
return await test(5).ConfigureAwait(false);
}
工作正常。然后我测试了这个:
public async Task<int> wrap()
{
int i = await test(5).ConfigureAwait(false);
int j = i + await test(3);
return j;
}
第一次单击按钮时它正在工作,但第二次点击时再次出现死锁。如果我在test(3)之后添加了ConfigureAwait(false),就像这样:
public async Task<int> wrap()
{
int i = await test(5).ConfigureAwait(false);
int j = i + await test(3).ConfigureAwait(false);
return j;
}
它再次起作用,但这对我没有意义。由于第一个ConfigureAwait(false),wrap()中的所有以下同步部分都应该在非UI线程上运行。我不明白为什么第二个ConfigureAwait(false)是必要的。
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);
}
}
几次单击后,表单冻结,输出为:
1.button1_Click(): 8
1.wrap(): 8
test(5): 13
2.wrap(): 8
2.button1_Click(): 8
test(3): 13
令我惊讶的是,“2.wrap():”与“1.wrap():”在同一个线程中运行,而不是“test(5)”。似乎ConfigureAwait(false)之后的代码也可以跳回UI线程。
答案 0 :(得分:4)
这是一个死锁 - 在单线程调度程序中运行的两个Task
正在等待彼此完成。
理解这一点有两件非常重要的事情:
await
的结果会在与开始时相同的调度程序中返回。await
可用的直接调度程序是调度到此事件循环的调度程序。在此调度程序中,一次只能执行一个任务。由于这些原因,您需要特别小心如何调用在UI调度程序内部执行的Task
上的阻塞方法。
在您的第二个示例中,对Result
的调用正在等待wrap
中的继续设置完成。延续计划在UI线程上运行,对Result
的调用恰好阻塞,因此都不会完成。
答案 1 :(得分:3)
你造成我explain in detail on my blog的僵局。总之,await
关键字将(默认情况下)捕获await
之前的当前上下文,并在该上下文中恢复其余async
方法。在这种情况下,“上下文”是UI上下文,它在UI线程上执行代码。因此,当您的代码调用Result
时,它会阻止UI线程,并且当await
完成时,它无法在UI线程上执行wrap
的其余部分。因此,僵局。
避免此死锁的最佳方法是使用await
代替Result
或Wait
进行异步任务。即,将您的点击方法更改为:
private async void button1_Click(object sender, EventArgs e)
{
Task<int> t = wrap();
Console.WriteLine(await t);
}