我很困惑为什么这两个程序的输出不同:
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更新发生。为什么第二组代码允许它同时发生?
这不是你想的那样:
await Task.Run(() => LongProcess());
代码正在等待Task.Run()
,但中没有任务正在等待LongProcess()
。因此,在这种情况下,Task.Run()
立即返回。
这实际上是一个很有趣的例子,说明了"完全异步"的失败,因为内联函数本质上是隐藏了异步的事实。事实上,编译器应该警告您没有任何东西在等待LongProcess()
,并且它会立即返回。
与此对比:
await Task.Run(async () => await LongProcess());
编辑:我刚刚注意到为什么编译器可能没有警告你。因为:
async void
永远,永远,永远不要这样做:)(好吧,有一个这样做的正当理由。我敢肯定,直到今天c#团队都因为这个原因而支持它。但除非你遇到那个原因,否则不要这样做。
对于异步方法总是返回一个Task
,以便等待该方法。
在第一个程序中,LongProcess返回一个Task
,并且Task.Run
正在包装它——基本上只是在默认调度器中启动它,而不是在当前所处的任何上下文中启动它。
你会注意到Task.Run
有重载专门做这个包装,并没有返回Task<Task>
。
在第二个程序中,LongProcess是一个async void
方法,这意味着Task.Run
没有什么要包装的,在保证工作完成之前,它或多或少会立即完成。