【问题标题】:await Task vs await Task.Run(voidMethod)等待任务与等待Task.Run(voidMethod)
【发布时间】:2015-06-02 02:09:41
【问题描述】:

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

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 的第一个示例更正确,但我不明白为什么将 void 函数包装在 Task.Run 中不会产生相同的输出?第一个函数符合我的预期,每 1 秒更新一次进度条。第二个代码尝试一次全部更新进度条,导致尝试从多个线程更新同一个 UI 元素时出现问题。

我的假设是,由于 buttonClick 方法等待漫长的过程完成,所以两组代码都不应允许 progressBar1 更新,直到前一个过程完成。为什么第二组代码允许它一次发生?

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    这不是你想的那样:

    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 Task.Run(Func&lt;Task&gt;),其中展开 @ 987654334@ 而不是返回Task&lt;Task&gt;。
    • @LucasTrzesniewski:你确定吗?我现在在 VS2013 中对此进行测试,其中我的 LongProcess 是一个简单的 async Task Foo() { await Task.Yield(); },当我以与 OP 代码相同的方式调用它时,我在 VS 中收到警告。
    • await Task.Run(() =&gt; Foo()); 对我没有任何警告,奇怪的是你得到了一个。
    • @LucasTrzesniewski:很有趣,我刚刚发现了不同之处。 await Task.Run(() =&gt; { Foo(); }); 产生警告。我忘记了我在添加 await 时将其包裹在 {} 中,并且在恢复此测试时没有删除它们。
    • 哦,是的,这是一个典型的错误,我曾经wrote a whole answer 犯过那个错误;-)
    【解决方案2】:

    在第一个程序中,LongProcess 返回一个Task,Task.Run 正在包装它——基本上只是在默认调度程序中启动它,而不是在您当前所在的任何上下文中启动它。

    您会注意到 Task.Run 具有专门用于执行此包装的重载,并且不会返回 Task&lt;Task&gt;。

    在第二个程序中,LongProcess 是一个async void 方法,这意味着Task.Run 没有要包装的内容,将或多或少立即完成,并且在保证完成工作之前完成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-11-12
      • 2016-02-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多