【问题标题】:Should one use Task.Run within another Task?应该在另一个任务中使用 Task.Run 吗?
【发布时间】:2018-09-09 14:58:55
【问题描述】:

我有很多工作要做(CPU 密集型或 IO 密集型,没有异步接口可用)。我想知道是否可以像这样在任务中完成所有非异步工作:

async Task DoSomeStuff()
{
    await SomethingAsync();
    …
    DoCpuBoundWork();
    …
    await SomethingElseAsync();
}

或者我应该像这样使用 Task.Run 吗?

async Task DoSomeStuff()
{
    await SomethingAsync();
    …
    await Task.Run(() => DoCpuBoundWork());
    …
    await SomethingElseAsync();
}

我知道任务不一定在另一个线程上执行,所以我想知道调度程序是否会假设任务是非阻塞的,这将使在 Task.Run 之外执行 CPU 密集型工作减慢应用程序的速度.例如,如果调度程序决定将 CPU 密集型工作调度到应用的 UI 线程,则可能会出现减速。

我确定以前有人问过这个问题,但我找不到合适的关键字来搜索它,很抱歉。

【问题讨论】:

    标签: c# async-await task-parallel-library


    【解决方案1】:

    this page 中所述(深入了解任务和任务 一节以了解 CPU 绑定操作),任务在调用它们的线程上运行,并且 CPU 绑定工作确实应该包含在Task.Run 所以它将在另一个(后台)线程上运行。所以是的,在异步方法中使用 Task.Run 来进行 CPU 绑定或其他阻塞工作是正常的。引自页面:

    public async Task<int> CalculateResult(InputData data)
    {
        // This queues up the work on the threadpool.
        var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));
    
        // Note that at this point, you can do some other work concurrently,
        // as CalculateResult() is still executing!
    
        // Execution of CalculateResult is yielded here!
        var result = await expensiveResultTask;
    
        return result;
    }
    

    CalculateResult() 在调用它的线程上执行。当它调用 Task.Run 时,它会将昂贵的 CPU 绑定操作 DoExpensiveCalculation() 排入线程池并接收任务句柄。 DoExpensiveCalculation() 最终在下一个可用线程上并发运行,可能在另一个 CPU 内核上。可以在 DoExpensiveCalculation() 忙于另一个线程时进行并发工作,因为调用 CalculateResult() 的线程仍在执行。

    一旦遇到 await,CalculateResult() 的执行就会交给它的调用者,允许在 DoExpensiveCalculation() 生成结果时使用当前线程完成其他工作。完成后,结果将排队等待在主线程上运行。最终,主线程将返回执行CalculateResult(),此时会得到DoExpensiveCalculation()的结果。

    【讨论】:

      【解决方案2】:

      答案是:视情况而定。

      您指出了调度程序,这正是问题所在。如果您在线程池(默认调度程序)上运行,那么运行同步的 CPU 密集型工作非常好。如果您在 UI 线程上运行,这可能会导致糟糕的用户体验。

      也就是说,虽然这是一个问题,但这不是你的问题。通过实施DoSomeStuff,您的合同是您将在当前调度程序上运行一些工作。由调用者决定这是否可能是一个问题,在这种情况下添加一个Task.Run 以抵消对另一个调度程序的调用。

      简而言之,不要使用await Task.Run(() =&gt; DoCpuBoundWork());。让知道执行上下文的调用者为您决定。

      【讨论】:

      • 调用者应该做什么很清楚。我提供了一个等待的异步方法,他们应该等待我的方法。调用者不会做await Task.Run(async () =&gt; await DoSomeStuff());
      • @Arshia001 为什么不呢?如果调用者有理由相信该方法将运行一些昂贵的计算并且不希望在主线程上发生这种情况,这就是Task.Run 的目的。而await Task.Run(DoSomeStuff)就足够了,不需要在lambda中等待
      • 因为这是您通常使用异步方法所做的事情,所以您等待它们并且不再考虑它。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-07
      • 1970-01-01
      • 2011-02-07
      • 2018-03-21
      • 2018-07-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多