await Task vs await Task.Run(voidMethod)

时间:2015-06-01 18:00:38

标签: c# asynchronous async-await

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

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更新发生,直到上一个过程完成。为什么第二组代码允许它同时发生?

2 个答案:

答案 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无需包装,并且会在保证完成工作之前或多或少地完成。