我的问题来自this article的一个例子:
private void button1_Click(object sender, EventArgs e) { button1.Text = await Task.Run(async delegate { string data = await DownloadAsync(); return Compute(data); }); }
这是我的心理模型告诉我将使用此代码发生的事情。一个 用户单击button1,导致UI框架调用button1_Click 在UI线程上。然后代码启动一个工作项来运行 ThreadPool(通过Task.Run)。该工作项开始一些下载工作 并异步等待它完成。后续工作项目 然后在ThreadPool上执行一些计算密集型操作 下载的结果,并返回结果,导致任务 正在等待UI线程完成。那时,用户界面 线程处理这个button1_Click方法的剩余部分,存储 计算结果到button1的Text属性。
如果SynchronizationContext不作为部分流动,我的期望是有效的 ExecutionContext。然而,如果它确实流动,我会非常痛苦 失望。 Task.Run在调用时捕获ExecutionContext,和 用它来运行传递给它的委托。这意味着UI SynchronizationContext,当调用Task.Run时是当前的 将流入Task,并在调用时为Current DownloadAsync并等待生成的任务。那意味着 await将看到Current SynchronizationContext并发布 其余的异步方法作为延续运行的 UI线程。这意味着我的计算方法很有可能 在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
中的代码以使用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上下文方法不会将UI上下文作为其当前上下文,因此不需要使用ConfigureAwait(false)
关于在UI上下文中调用它的机会。这样做的好处是所有内部非UI代码都不需要使用ConfigureAwait(false)
,即使它是从事件处理程序调用的,并且所有上下文感知代码仅限于UI代码,例如事件处理程序。 / p>
答案 0 :(得分:3)
首先关闭;不要在这里使用异步委托。你不必要地使你的代码变得复杂。
当你有一个已经异步的操作时,你希望获得await
的结果。如果您必须执行CPU绑定工作,请在Task.Run
调用中运行该工作并await
以获得结果。
private void button1_Click(object sender, EventArgs e)
{
string data = await DownloadAsync();
button1.Text = await Task.Run(() => Compute(data));
}
这里很清楚发生了什么。获取异步操作的结果,使用它在非UI线程中执行一些CPU绑定工作,然后在UI上下文中使用该结果。
现在,说了所有这些,我希望你的代码实际上可以正常工作(假设当前上下文不是整个Task.Run
委托中的UI上下文),但它是一个很难理解代码或看到它正在做什么。