await Task vs await Task.Run(voidMethod)



我很困惑为什么这两个程序的输出不同:

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没有什么要包装的,在保证工作完成之前,它或多或少会立即完成。

最新更新