我用这个例子来阅读这篇文章:
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
一个不完整的操作时,将捕获同步上下文(如果存在)。然后,当异步工作完成重新激活时,将使用捕获的同步上下文封送该工作。许多场景没有同步上下文,但UI应用程序,如winforms和WPF有,并且同步上下文代表UI线程(基本上:这是为了默认行为变成"我的代码工作")。而不是"当我在async
方法中与UI交谈时,我得到了跨线程异常"。本质上,这意味着当您从UI线程中await Task.Delay(1000)
时,它会暂停1000ms(释放UI线程),但随后在UI线程上恢复。这意味着你"吨here"工作要做;发生在UI线程上,阻塞了它。
通常,解决这个问题很简单:添加.ConfigureAwait(false)
,它禁用同步上下文捕获:
await Task.Delay(1000).ConfigureAwait(false);
(通常:在allawait
方法之后添加这个)
注意,只有当你知道你的不需要同步上下文提供的时才应该这样做。特别是,你不会在之后尝试触摸UI。如果你需要与UI对话,你必须显式地使用UI分派器。
当你使用Task.Run(...)
时,你通过不同的路由转义了sync-context——即不启动UI线程(sync-context是通过活动线程指定的)。