ConfigureAwait 在 .NET 中的使用
Usage of ConfigureAwait in .NET
我在很多地方读过 ConfigureAwait(包括 SO 问题),这里是我的结论:
- ConfigureAwait(true):在等待 运行 之前的代码在同一线程上运行其余代码。
- ConfigureAwait(false):在等待代码 运行 所在的同一线程上运行其余代码。
- 如果 await 后跟访问 UI 的代码,则任务应附加
.ConfigureAwait(true)
。否则,由于另一个线程访问 UI 个元素,将发生 InvalidOperationException。
我的问题是:
- 我的结论正确吗?
- ConfigureAwait(false) 何时提高性能,何时提高性能?
- 如果为 GUI 应用程序编写,但下一行不访问 UI 元素。我应该使用 ConfigureAwait(false) 还是 ConfigureAwait(true)?
ConfigureAwait(false)
如果可用的工作线程不多并且需要等待的线程一直很忙,则可能会提高性能。
ConfigureAwait(false)
在任何不需要返回相同 SynchronizationContext(通常与线程链接)的地方都被推荐,尤其是在内部等待某些东西的库中:https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f.
ConfigureAwait(true)
(默认设置)在您需要相同的上下文时是必需的,但在某些情况下也可能导致死锁。
考虑这段代码:
void Main()
{
// creating a windows form attaches a synchronization context to the current thread
new System.Windows.Forms.Form();
var task = DoSth();
Console.WriteLine(task.Result);
}
async Task<int> DoSth()
{
await Task.Delay(1000);
return 1;
}
在此示例中,由于未等待任务 DoSth,主 UI 线程因等待 task.Result 而被阻塞 - 同时 DoSth 被阻塞,因为它想要返回到 UI延迟后的线程。这会导致死锁,这段代码永远不会执行到最后。添加 .ConfigureAwait(false)
解决了这种情况下的问题。
在应用程序代码中使用 ConfigureAwait(false)
通常不会以任何有意义的方式提升应用程序的性能,因为通常您不会 await
在应用程序代码的内部循环中。例如,假设您的应用程序有一个按钮,每次用户单击该按钮时都会启动一个异步操作,并且该异步操作包含一个 await
。通过在此 await
之后键入 22 个字符 .ConfigureAwait(false)
,您已经失去了可比的生命时间,而您希望从 10 个每分钟单击此按钮一次的用户中节省时间,即每天 8 小时,每次 20 年(总共约 35,000,000 次上下文切换 = CPU 处理时间的几秒)。
而这之前考虑到你需要考虑是否可以安全地包含这个配置的时间(取决于continuation是否包含UI相关代码),你需要的时间是为了每次你必须 maintain/modify 代码时重新确认你以前的评估,以及你在调试时会损失的时间,以防你的评估错误。
另一方面,如果您的 Button_Click
处理程序包含如下代码:
private async void Button_Click(object sender, EventArgs e)
{
var client = new WebClient();
using var stream = await client.OpenReadTaskAsync("someUrl");
var buffer = new byte[1024];
while ((await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
//...
}
}
...那么一定要花额外的时间来 ConfigureAwait(false)
ReadAsync
任务。还要考虑通过将流读取部分移动到单独的异步方法来重构代码,这样您就可以安全地访问 Button_Click
处理程序内任何位置的 UI 元素,而不会被不包含的技术分心属于app的这一层。
更直接地回答您的问题:
ConfigureAwait(true): Runs the rest of the code on the same thread the code before the await was run on.
不一定是同一个线程,而是同一个同步上下文。同步上下文可以决定如何 运行 代码。在 UI 应用程序中,它将是同一个线程。在 ASP.NET 中,它可能不是同一个线程,但您将有 HttpContext
可用,就像以前一样。
ConfigureAwait(false): Runs the rest of the code on the same thread the awaited code was run on.
这是不正确的。 ConfigureAwait(false)
告诉它不需要上下文,所以代码可以是 运行 anywhere。它可以是 运行 的任何线程。
If the await is followed by a code that accesses the UI, the task should be appended with .ConfigureAwait(true)
. Otherwise, an InvalidOperationException will occur due to another thread accessing UI elements.
“应该附加.ConfigureAwait(true)
”是不正确的。 ConfigureAwait(true)
是默认值。所以如果那是你想要的,你不需要指定它。
- When does ConfigureAwait(false) improves performance, and when it doesn't?
返回同步上下文可能需要一些时间,因为它可能必须等待其他事情完成 运行ning。实际上,这种情况很少发生,或者说等待时间微乎其微,您根本不会注意到。
- If writing for a GUI application, but the next lines doesn't access the UI elements. Should I use ConfigureAwait(false) or ConfigureAwait(true) ?
您可以使用ConfigureAwait(false)
,但我建议您不要使用,原因如下:
- 我怀疑您会注意到任何性能改进。
- 它可以引入您可能意想不到的并行性。如果您使用
ConfigureAwait(false)
,延续可以在任何线程上 运行,因此如果您正在访问非线程安全对象,您可能会遇到问题。出现这些问题并不常见,但它可能会发生。
- 您(或维护此代码的其他人)稍后可以添加与 UI 交互的代码,并且会抛出异常。希望
ConfigureAwait(false)
很容易被发现(它可能与抛出异常的方法不同)并且 you/they 知道它的作用。
我发现完全不使用 ConfigureAwait(false)
更容易(图书馆除外)。用 ConfigureAwait FAQ:
中 Stephen Toub(微软员工)的话来说
When writing applications, you generally want the default behavior (which is why it is the default behavior).
编辑: 我自己写了一篇关于这个主题的文章:.NET: Don’t use ConfigureAwait(false)
我在很多地方读过 ConfigureAwait(包括 SO 问题),这里是我的结论:
- ConfigureAwait(true):在等待 运行 之前的代码在同一线程上运行其余代码。
- ConfigureAwait(false):在等待代码 运行 所在的同一线程上运行其余代码。
- 如果 await 后跟访问 UI 的代码,则任务应附加
.ConfigureAwait(true)
。否则,由于另一个线程访问 UI 个元素,将发生 InvalidOperationException。
我的问题是:
- 我的结论正确吗?
- ConfigureAwait(false) 何时提高性能,何时提高性能?
- 如果为 GUI 应用程序编写,但下一行不访问 UI 元素。我应该使用 ConfigureAwait(false) 还是 ConfigureAwait(true)?
ConfigureAwait(false)
如果可用的工作线程不多并且需要等待的线程一直很忙,则可能会提高性能。
ConfigureAwait(false)
在任何不需要返回相同 SynchronizationContext(通常与线程链接)的地方都被推荐,尤其是在内部等待某些东西的库中:https://medium.com/bynder-tech/c-why-you-should-use-configureawait-false-in-your-library-code-d7837dce3d7f.
ConfigureAwait(true)
(默认设置)在您需要相同的上下文时是必需的,但在某些情况下也可能导致死锁。
考虑这段代码:
void Main()
{
// creating a windows form attaches a synchronization context to the current thread
new System.Windows.Forms.Form();
var task = DoSth();
Console.WriteLine(task.Result);
}
async Task<int> DoSth()
{
await Task.Delay(1000);
return 1;
}
在此示例中,由于未等待任务 DoSth,主 UI 线程因等待 task.Result 而被阻塞 - 同时 DoSth 被阻塞,因为它想要返回到 UI延迟后的线程。这会导致死锁,这段代码永远不会执行到最后。添加 .ConfigureAwait(false)
解决了这种情况下的问题。
在应用程序代码中使用 ConfigureAwait(false)
通常不会以任何有意义的方式提升应用程序的性能,因为通常您不会 await
在应用程序代码的内部循环中。例如,假设您的应用程序有一个按钮,每次用户单击该按钮时都会启动一个异步操作,并且该异步操作包含一个 await
。通过在此 await
之后键入 22 个字符 .ConfigureAwait(false)
,您已经失去了可比的生命时间,而您希望从 10 个每分钟单击此按钮一次的用户中节省时间,即每天 8 小时,每次 20 年(总共约 35,000,000 次上下文切换 = CPU 处理时间的几秒)。
而这之前考虑到你需要考虑是否可以安全地包含这个配置的时间(取决于continuation是否包含UI相关代码),你需要的时间是为了每次你必须 maintain/modify 代码时重新确认你以前的评估,以及你在调试时会损失的时间,以防你的评估错误。
另一方面,如果您的 Button_Click
处理程序包含如下代码:
private async void Button_Click(object sender, EventArgs e)
{
var client = new WebClient();
using var stream = await client.OpenReadTaskAsync("someUrl");
var buffer = new byte[1024];
while ((await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
//...
}
}
...那么一定要花额外的时间来 ConfigureAwait(false)
ReadAsync
任务。还要考虑通过将流读取部分移动到单独的异步方法来重构代码,这样您就可以安全地访问 Button_Click
处理程序内任何位置的 UI 元素,而不会被不包含的技术分心属于app的这一层。
更直接地回答您的问题:
ConfigureAwait(true): Runs the rest of the code on the same thread the code before the await was run on.
不一定是同一个线程,而是同一个同步上下文。同步上下文可以决定如何 运行 代码。在 UI 应用程序中,它将是同一个线程。在 ASP.NET 中,它可能不是同一个线程,但您将有 HttpContext
可用,就像以前一样。
ConfigureAwait(false): Runs the rest of the code on the same thread the awaited code was run on.
这是不正确的。 ConfigureAwait(false)
告诉它不需要上下文,所以代码可以是 运行 anywhere。它可以是 运行 的任何线程。
If the await is followed by a code that accesses the UI, the task should be appended with
.ConfigureAwait(true)
. Otherwise, an InvalidOperationException will occur due to another thread accessing UI elements.
“应该附加.ConfigureAwait(true)
”是不正确的。 ConfigureAwait(true)
是默认值。所以如果那是你想要的,你不需要指定它。
- When does ConfigureAwait(false) improves performance, and when it doesn't?
返回同步上下文可能需要一些时间,因为它可能必须等待其他事情完成 运行ning。实际上,这种情况很少发生,或者说等待时间微乎其微,您根本不会注意到。
- If writing for a GUI application, but the next lines doesn't access the UI elements. Should I use ConfigureAwait(false) or ConfigureAwait(true) ?
您可以使用ConfigureAwait(false)
,但我建议您不要使用,原因如下:
- 我怀疑您会注意到任何性能改进。
- 它可以引入您可能意想不到的并行性。如果您使用
ConfigureAwait(false)
,延续可以在任何线程上 运行,因此如果您正在访问非线程安全对象,您可能会遇到问题。出现这些问题并不常见,但它可能会发生。 - 您(或维护此代码的其他人)稍后可以添加与 UI 交互的代码,并且会抛出异常。希望
ConfigureAwait(false)
很容易被发现(它可能与抛出异常的方法不同)并且 you/they 知道它的作用。
我发现完全不使用 ConfigureAwait(false)
更容易(图书馆除外)。用 ConfigureAwait FAQ:
When writing applications, you generally want the default behavior (which is why it is the default behavior).
编辑: 我自己写了一篇关于这个主题的文章:.NET: Don’t use ConfigureAwait(false)