我很困惑为什么这两个程序的输出不同:
private async void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 33; i++)
{
await LongProcess();
}
}
private async Task LongProcess()
{
await Task.Delay(1000);
progressBar1.Value += 3;
}
和
private async void button1_Click(object sender, EventArgs e)
{
for (int i = 0; i < 33; i++)
{
await Task.Run(() => LongProcess());
}
}
private async void LongProcess()
{
await Task.Delay(1000);
progressBar1.Value += 3;
}
我意识到返回Task
的第一个示例更正确,但我不明白为什么在Task.Run
中包装void函数不会产生相同的输出?第一个功能完成我的预期,每1秒更新一次进度条。第二个代码尝试一次更新进度条,导致尝试从多个线程更新相同的UI元素时出现问题。
我的假设是,自从buttonClick方法等待漫长的过程完成后,两组代码都不允许progressBar1更新发生,直到上一个过程完成。为什么第二组代码允许它同时发生?
答案 0 :(得分:6)
这不是你认为的那样:
await Task.Run(() => LongProcess());
代码正在等待Task.Run()
,但中没有任何正在等待LongProcess()
。因此Task.Run()
会在这种情况下立即返回。
这实际上是一个有趣的例子,说明未能一直向下异步&#34;因为内联函数本质上是隐藏它是异步的事实。事实上,编译器应该警告你没有任何东西在等待LongProcess()
并且它会立即返回。
与此对比:
await Task.Run(async () => await LongProcess());
编辑:我刚才注意到为什么编译器可能没有警告你。因此:
async void
从来没有,永远这样做:)(好吧,好吧,有一个这样做的正当理由。而且我确信这会困扰C#团队那天他们不得不支持它只是出于这个原因。但除非你遇到这个原因,否则不要这样做。)
始终为异步方法返回Task
,以便等待该方法。
答案 1 :(得分:1)
在第一个程序中,LongProcess返回Task
,而Task.Run
正在包装它 - 基本上只是在默认调度程序中启动它,而不是你当前所处的任何上下文。
您会注意到Task.Run
具有专门用于执行此换行的重载,并且未返回Task<Task>
。
在第二个程序中,LongProcess是一个async void
方法,这意味着Task.Run
无需包装,并且会在保证完成工作之前或多或少地完成。