为什么不任务<>.在这种情况下,结果工作
本文关键字:结果 工作 这种情况下 任务 为什么不 | 更新日期: 2023-09-27 18:28:47
使用以下代码可以正常工作:
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,它会按预期工作。
更新1:这两个答案都是正确的,但我只能标记一个作为答案。基于对这个问题的理解,我做了进一步的测试。我在包装中使用了配置等待方法来让延续在 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;
}
当我第一次单击按钮时,它正在工作,但在第二次单击时再次死锁。如果我在测试(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 线程上运行。我不明白为什么第二个配置等待(假(是必要的。
更新2:
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 线程。
死锁 - 在单线程调度程序中运行的两个Task
正在等待彼此完成。
理解这一点有两件非常重要的事情:
- 默认情况下,
await
的结果返回到与启动时相同的调度程序。 - UI 在单线程事件循环上运行,可用于在 UI 调用中
await
的即时计划程序是调度到此事件循环的计划程序。在此调度程序中,一次只能执行一个任务。
因此,您需要特别小心如何在 UI 的计划程序内执行的Task
上调用阻止方法。
在第二个示例中,对 Result
的调用正在等待 wrap
中的延续设置完成。延续计划在 UI 线程上运行,对 Result
的调用恰好阻塞了该线程,因此两者都不会完成。
你造成了一个僵局,我在我的博客上详细解释了。总之,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);
}