I understand that it's recommended to use ConfigureAwait(false)
for await
s in library code so that subsequent code does not run in the caller's execution context, which could be a UI thread. I also understand that await Task.Run(CpuBoundWork)
should be used instead of CpuBoundWork()
for the same reason.
我理解,建议在库代码中使用ConfigureAwait(false)来等待后续代码不在调用者的执行上下文中运行,后者可能是UI线程。我也明白,出于同样的原因,应该使用等待Task.Run(CpuBoundWork)而不是CpuBoundWork()。
Example with ConfigureAwait
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address).ConfigureAwait(false))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync().ConfigureAwait(false))
return LoadHtmlDocument(contentStream); //CPU-bound
}
Example with Task.Run
public async Task<HtmlDocument> LoadPage(Uri address)
{
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
return await Task.Run(async () =>
{
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
});
}
What are the differences between these two approaches?
这两种方法有什么区别?
4 个解决方案
#1
67
When you say Task.Run
, you are saying that you have some CPU work to do that may take a long time, so it should always be run on a thread pool thread.
当你说Task.Run,你说你有一些CPU工作可能需要很长时间,所以它应该始终在线程池线程上运行。
When you say ConfigureAwait(false)
, you are saying that the rest of that async
method does not need the original context. ConfigureAwait
is more of an optimization hint; it does not always mean that the continuation is run on a thread pool thread.
当您说ConfigureAwait(false)时,您说该异步方法的其余部分不需要原始上下文。 ConfigureAwait更像是一个优化提示;它并不总是意味着继续在线程池线程上运行。
#2
16
In this case, your Task.Run
version will have a bit more overhead, as the first await call (await client.GetAsync(address)
) will still marshal back into the calling context, as will the results of the Task.Run
call.
在这种情况下,您的Task.Run版本将有更多的开销,作为第一个调用的await(等待client.GetAsync(地址))将仍然编组回调用环境,如将在本Task.Run调用的结果。
In the first example, on the other hand, your first Async()
method is configured to not require marshaling back into the calling context, which allows the continuation to run on a background thread still. As such, there won't be any marshaling back into the caller's context.
另一方面,在第一个示例中,您的第一个Async()方法被配置为不需要编组回调用上下文,这允许继续在后台线程上运行。因此,不会有任何编组回到调用者的上下文中。
#3
2
Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label
同意@Stephen的回答,如果还是混乱,请看下面的截图1#没有ConfigureAwait(false)见下图图片主线程试图更新Label
2# With ConfigureAwait(false)
See below image working thread trying to update label
2#With ConfigureAwait(false)参见下图图片工作线程试图更新标签
#4
1
As a side note, in both cases LoadPage()
could still block your UI thread, because await client.GetAsync(address)
needs time to create a task to pass to ConfigureAwait(false)
. And your time consuming operation might have already started before task is returned.
作为旁注,在两种情况下,LoadPage()仍然可以阻止您的UI线程,因为等待client.GetAsync(地址)需要时间来创建传递给ConfigureAwait(false)的任务。在返回任务之前,您的耗时操作可能已经开始。
One possible solution is to use SynchronizationContextRemover
from here:
一种可能的解决方案是从这里使用SynchronizationContextRemover:
public async Task<HtmlDocument> LoadPage(Uri address)
{
await new SynchronizationContextRemover();
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
}
#1
67
When you say Task.Run
, you are saying that you have some CPU work to do that may take a long time, so it should always be run on a thread pool thread.
当你说Task.Run,你说你有一些CPU工作可能需要很长时间,所以它应该始终在线程池线程上运行。
When you say ConfigureAwait(false)
, you are saying that the rest of that async
method does not need the original context. ConfigureAwait
is more of an optimization hint; it does not always mean that the continuation is run on a thread pool thread.
当您说ConfigureAwait(false)时,您说该异步方法的其余部分不需要原始上下文。 ConfigureAwait更像是一个优化提示;它并不总是意味着继续在线程池线程上运行。
#2
16
In this case, your Task.Run
version will have a bit more overhead, as the first await call (await client.GetAsync(address)
) will still marshal back into the calling context, as will the results of the Task.Run
call.
在这种情况下,您的Task.Run版本将有更多的开销,作为第一个调用的await(等待client.GetAsync(地址))将仍然编组回调用环境,如将在本Task.Run调用的结果。
In the first example, on the other hand, your first Async()
method is configured to not require marshaling back into the calling context, which allows the continuation to run on a background thread still. As such, there won't be any marshaling back into the caller's context.
另一方面,在第一个示例中,您的第一个Async()方法被配置为不需要编组回调用上下文,这允许继续在后台线程上运行。因此,不会有任何编组回到调用者的上下文中。
#3
2
Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label
同意@Stephen的回答,如果还是混乱,请看下面的截图1#没有ConfigureAwait(false)见下图图片主线程试图更新Label
2# With ConfigureAwait(false)
See below image working thread trying to update label
2#With ConfigureAwait(false)参见下图图片工作线程试图更新标签
#4
1
As a side note, in both cases LoadPage()
could still block your UI thread, because await client.GetAsync(address)
needs time to create a task to pass to ConfigureAwait(false)
. And your time consuming operation might have already started before task is returned.
作为旁注,在两种情况下,LoadPage()仍然可以阻止您的UI线程,因为等待client.GetAsync(地址)需要时间来创建传递给ConfigureAwait(false)的任务。在返回任务之前,您的耗时操作可能已经开始。
One possible solution is to use SynchronizationContextRemover
from here:
一种可能的解决方案是从这里使用SynchronizationContextRemover:
public async Task<HtmlDocument> LoadPage(Uri address)
{
await new SynchronizationContextRemover();
using (var client = new HttpClient())
using (var httpResponse = await client.GetAsync(address))
using (var responseContent = httpResponse.Content)
using (var contentStream = await responseContent.ReadAsStreamAsync())
return LoadHtmlDocument(contentStream); //CPU-bound
}