61

I understand that it's recommended to use ConfigureAwait(false) for awaits 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?

Sam
  • 40,644
  • 36
  • 176
  • 219

4 Answers4

76

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 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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    I was reading your article at http://blog.stephencleary.com/2012/02/async-and-await.html and I got a bit confused around ConfigureWait(false) so ended here. Here you say "the rest of the async method does not the original context" which somewhat clarifies things for me. Tho can you pls give an example of a "not needed context" so I can solidify my understanding? – rism Jun 05 '14 at 06:55
  • 6
    @rism: By that I mean anything that *doesn't* need the context. In this case, `LoadHtmlDocument` is parsing the HTML; this parsing doesn't have to be done on the UI thread, so it doesn't need the context. – Stephen Cleary Jun 05 '14 at 12:29
  • 1
    @Stephen Cleary,could you please provide example when the rest of async method need context? – vborutenko Jan 30 '16 at 10:08
  • 4
    @cosset: Sure. Since this is a UI app, an example of needing context would be updating a UI control. For example, if the end of the `async` method updated a `Label` or `ProgressBar`, then it would need to resume on the UI context. If it didn't, it would get a cross-thread exception. – Stephen Cleary Jan 30 '16 at 12:38
  • @StephenCleary does `ConfigureAwait(false)` actually mean that the original context (I suppose you mean SynchronizationContext) is not needed or does it mean that the continuation *should not and will not* be executed in the original synchronization context. I think the latter is the case. If it was just a hint, it means there is no way to determine whether the continuation will be executed in the original synchronization context, or not, thus introducing undefined behavior, and probably deadlocks. – Thanasis Ioannidis Mar 22 '17 at 10:36
  • 1
    @ThanasisIoannidis: I mean "context" as defined in my [`async` intro](http://blog.stephencleary.com/2012/02/async-and-await.html): the current `SynchronizationContext` or `TaskScheduler`. It is a hint, but it *will* take effect if the task has not yet completed. It does *not* change contexts if the task is already completed (e.g., result was read from a cache). If you have code that *must* run on the thread pool, `Task.Run` is the more appropriate tool. – Stephen Cleary Mar 22 '17 at 13:32
  • 1
    @StephenCleary thanks for clarifying that. So the hint always takes effect on non-completed tasks, but never takes effect on already completed tasks. So what are you saying here is that if I await on a task and the continuation of that task *must* run on the thread pool, `ConfigureAwait(false)` is not enough because if the original task is already completed, the continuation will not switch content, right? In that case I must wrap the continuation of the task in a `Task.Run` to ensure execution in the ThreadPool. – Thanasis Ioannidis Mar 23 '17 at 12:03
  • will GetDataAsync2 run in the UI Context, or whatever context is convenient if ConfigureAwait(false) is added at the end of the returned task? – NStuke Feb 14 '18 at 00:58
  • @NStuke: What `GetDataAsync2` are you referring to? – Stephen Cleary Feb 14 '18 at 01:19
  • Sorry, I was looking at the code from @pankaj-rawat example. In the OP example with ConfigureAwait, my question would refer to what context does client.GetAsync(address) execute in? – NStuke Feb 16 '18 at 01:29
  • 1
    @NStuke: That would be the same context `LoadPage` starts executing in. – Stephen Cleary Feb 16 '18 at 02:29
  • Please could you give a simple example of when you say - `it does not always mean that the continuation is run on a thread pool thread` - do you mean that it may reuse this existing thread? When does this happen? Is it when the current thread is free? But then if it's free then it means it's in the threadpool.. bit confused here. – variable Aug 01 '21 at 14:24
  • @variable: It's clarified in the comments above. If the task is already completed, then `await` doesn't wait for anything, and `ConfigureAwait` has no effect. – Stephen Cleary Aug 01 '21 at 15:25
  • Suppose I have tasks added to a list via for loop. Assuming each task has an await Task.Delay configure await false, followed by some syncronous code. I want to ask - suppose more than 1 task have completed the await, then will the code following await run in parallel? – variable Aug 01 '21 at 18:38
  • @variable: I'm not clear on what your question is. Can you post it - with code - as an actual question? – Stephen Cleary Aug 01 '21 at 19:36
17

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.

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.

Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
5

Agreed @Stephen answer, If still confusion see below screenshots 1# Without ConfigureAwait(false)
See below image Main thread trying to update Label enter image description here

2# With ConfigureAwait(false)
See below image working thread trying to update label enter image description here

Pankaj Rawat
  • 4,037
  • 6
  • 41
  • 73
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.

One possible solution is to use SynchronizationContextRemover from here:

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
}
Roman Gudkov
  • 3,503
  • 2
  • 20
  • 20
  • I tried this and it worked. My only question is. Am I always need to use await new sync..... in every thread that has await? or I only needed this on my httpclient class? – zackmark15 Jul 30 '20 at 18:05