在基于 UI 的应用程序中使用 await Task.Run(() => someMethodAsync()) 与 await someMethodAsync()

Using await Task.Run(() => someMethodAsync()) vs await someMethodAsync() in a UI based app

我用这个例子阅读了这个article

class MyService
{
  /// <summary>
  /// This method is CPU-bound!
  /// </summary>
  public async Task<int> PredictStockMarketAsync()
  {
    // Do some I/O first.
    await Task.Delay(1000);

    // Tons of work to do in here!
    for (int i = 0; i != 10000000; ++i)
      ;

    // Possibly some more I/O here.
    await Task.Delay(1000);

    // More work.
    for (int i = 0; i != 10000000; ++i)
      ;

    return 42;
  }
}

然后描述了如何调用它,具体取决于您使用的是基于 UI 的应用程序还是 ASP.NET 应用程序:

//UI based app:
private async void MyButton_Click(object sender, EventArgs e)
{
  await Task.Run(() => myService.PredictStockMarketAsync());
}

//ASP.NET:
public class StockMarketController: Controller
{
  public async Task<ActionResult> IndexAsync()
  {
    var result = await myService.PredictStockMarketAsync();
    return View(result);
  }
}

为什么在基于UI的应用程序中需要使用Task.Run()来执行PredictStockMarketAsync()

在基于 UI 的应用程序中使用 await myService.PredictStockMarketAsync(); 不会也不会阻塞 UI 线程吗?

默认情况下,当您 await 一个不完整的操作时,会捕获同步上下文(如果存在)。然后,当异步工作完成重新激活时,捕获的同步上下文用于编组工作。很多场景是没有sync-context的,但是winforms、WPF等UI应用,sync-context代表UI线程(基本上:这样一来,默认行为就变成了“我的代码有效”,而不是“在我的 async 方法中与 UI 交谈时,我得到了跨线程异常”)。本质上,这意味着当您从 UI 线程 await Task.Delay(1000) 时,它会暂停 1000 毫秒(释放 UI 线程),然后在 UI 上恢复 线程。这意味着您“这里有大量工作要做”发生在 UI 线程上,阻塞了它。

通常,解决这个问题很简单:添加 .ConfigureAwait(false),禁用同步上下文捕获:

await Task.Delay(1000).ConfigureAwait(false);

(通常:在该方法中的 all await 之后添加)

请注意,只有当您知道自己不需要同步上下文提供的内容时才应该这样做。特别是:您之后不会尝试触摸 UI。如果您确实需要与UI对话,您将必须明确使用UI调度程序。

当您使用 Task.Run(...) 时,您通过 不同的 路由转义了同步上下文 - 即不在 UI 线程上启动(同步-上下文是通过活动线程指定的)。