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.
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?
Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label
2# With ConfigureAwait(false)
See below image working thread trying to update label
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 theTask.Run
call.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.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.When you say
ConfigureAwait(false)
, you are saying that the rest of thatasync
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.As a side note, in both cases
LoadPage()
could still block your UI thread, becauseawait client.GetAsync(address)
needs time to create a task to pass toConfigureAwait(false)
. And your time consuming operation might have already started before task is returned.One possible solution is to use
SynchronizationContextRemover
from here: