在基于 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 线程上启动(同步-上下文是通过活动线程指定的)。
我用这个例子阅读了这个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 线程上启动(同步-上下文是通过活动线程指定的)。