我的问题是由本文中的一个例子提出的:
private void button1_Click(object sender, EventArgs e) { button1.Text = await Task.Run(async delegate { string data = await DownloadAsync(); return Compute(data); }); }
这是我的心理模型告诉我的,这段代码会发生什么。用户单击 button1,导致 UI 框架在 UI 线程上调用 button1_Click。然后代码启动一个工作项以在 ThreadPool 上运行(通过 Task.Run)。该工作项开始一些下载工作并异步等待它完成。ThreadPool 上的后续工作项然后对该下载结果执行一些计算密集型操作,并返回结果,从而导致 UI 线程上正在等待的任务完成。此时,UI 线程处理此 button1_Click 方法的剩余部分,将计算结果存储到 button1 的 Text 属性中。
如果 SynchronizationContext 不作为 ExecutionContext 的一部分流动,我的期望是有效的。但是,如果它确实流动,我将非常失望。Task.Run 在调用时捕获 ExecutionContext,并使用它来运行传递给它的委托。这意味着在调用 Task.Run 时处于当前状态的 UI SynchronizationContext 将流入任务,并且在调用 DownloadAsync 并等待生成的任务时处于当前状态。这意味着 await 将看到 Current SynchronizationContext 和 Post 异步方法的其余部分作为继续在 UI 线程上运行。这意味着我的 Compute 方法很可能会在 UI 线程上运行,而不是在 ThreadPool 上运行,从而导致我的应用出现响应问题。
假设我有文章中的示例,而不是:
private void button1_Click(object sender, EventArgs e) {
button1.Text = await DownloadAndComputeAsync();
}
// Can't be changed
private async Task<string> DownloadAndComputeAsync() {
return await Task.Run(async delegate
{
string data = await DownloadAsync();
return Compute(data);
});
}
出于某种原因,我无法将代码更改DownloadAndComputeAsync
为 use ConfigureAwait(false)
,可能是因为它位于库中或我无法控制的其他代码中。
有没有办法可以禁止将 UI 的 SynchronizationContext 捕获作为 ExecutionContext 的一部分?
我在上面链接的文章提到了 Task.Run 的内部版本,它禁止捕获 SynchronizationContext,但它是内部版本。如果它不是内部的,你可以这样做:
private void button1_Click(object sender, EventArgs e) {
button1.Text = await Task.Run(DownloadAndComputeAsync, InternalOptions.DoNotCaptureSynchronizationContextInExecutionContext);
}
// Can't be changed
private async Task<string> DownloadAndComputeAsync() {
return await Task.Run(async delegate
{
string data = await DownloadAsync();
return Compute(data);
});
}
用例
我没有一个案例我知道我会想要/需要使用它,但是在阅读了我在顶部链接的文章之后,我可以想到几个可能有用的地方。
如果从 UI 上下文中我使用的库不使用
ConfigureAwait(false)
并最终在 UI 线程上做大量工作。诚然,如果可以的话,在这种情况下,我可能会停止使用该库。当前推荐的最佳实践似乎是以下之一
- 在任何地方使用
ConfigureAwait(false)
,除非您知道您需要回到当前上下文。这样做的缺点ConfigureAwait(false)
是非常丑陋并且对代码可读性产生负面影响(imo)。 - 仅在库代码中使用
ConfigureAwait(false)
,但在所有内部代码中注意是否可以从例如 UI 上下文中使用给定的异步方法,并且ConfigureAwait(false)
仅在绝对需要时使用。这似乎不太理想,因为它意味着类必须知道它们的异步方法是否可能被调用,例如,UI 上下文。
我想知道是否可能存在第三种可能性:在任何时候调用异步方法时都要求在内部代码中,例如,事件处理程序中的代码或类似的代码负责确保异步方法不会执行的 UI 上下文t 将 UI 上下文作为其当前上下文,因此如果
ConfigureAwait(false)
在 UI 上下文上调用它,则不需要使用它。这样做的好处是,所有内部非 UI 代码都不需要使用ConfigureAwait(false)
,即使它是从事件处理程序调用的,并且所有上下文感知代码都仅限于 UI 代码,例如事件处理程序。- 在任何地方使用