Revisiting Task.ConfigureAwait(continueOnCapturedContext: false)
When you're dealing with asynchronous operations, the overhead of a thread switch is way too small to care about (generally speaking). The purpose of ConfigureAwait(false)
is not to induce a thread switch (if necessary), but rather to prevent too much code running on a particular special context.
The reasoning behind this pattern was that "it helps to avoid deadlocks".
And stack dives.
But I do think this is a non-problem in the general case. When I encounter code that doesn't properly use ConfigureAwait
, I just wrap it in a Task.Run
and move on. The overhead of thread switches isn't worth worrying about.
ConfigureAwait(false) on Top Level Requests
Is it a high traffic website? One possible explanation might be that you're experiencing ThreadPool
starvation when you are not using ConfigureAwait(false)
. Without ConfigureAwait(false)
, the await
continuation is queued via AspNetSynchronizationContext.Post
, which implementation boils down to this:
Task newTask = _lastScheduledTask.ContinueWith(_ => SafeWrapCallback(action));
_lastScheduledTask = newTask; // the newly-created task is now the last one
Here, ContinueWith
is used without TaskContinuationOptions.ExecuteSynchronously
(I'd speculate, to make continuations truly asynchronous and reduce a chance for low stack conditions). Thus, it acquires a vacant thread from ThreadPool
to execute the continuation on. In theory, it might happen to be the same thread where the antecedent task for await
has finished, but most likely it'd be a different thread.
At this point, if ASP.NET thread pool is starving (or has to grow to accommodate a new thread request), you might be experiencing a delay. It's worth mentioned that the thread pool consists of two sub-pools: IOCP threads and worker threads (check this and this for some extra details). Your GetReportAsync
operations is likely to complete on an IOCP thread sub-pool, which doesn't seem to be starving. OTOH, the ContinueWith
continuation runs on a worker thread sub-pool, which appears to be starving in your case.
This is not going to happen in case ConfigureAwait(false)
is used all the way through. In that case, all await
continuations will run synchronously on the same threads the corresponding antecedent tasks have ended, be it either IOCP or worker threads.
You can compare the thread usage for both scenarios, with and without ConfigureAwait(false)
. I'd expect this number to be larger when ConfigureAwait(false)
isn't used:
catch (Exception ex)
{
Log("Total number of threads in use={0}",
Process.GetCurrentProcess().Threads.Count);
return Json("myerror", JsonRequestBehavior.AllowGet); // really slow without configure await
}
You can also try increasing the size of the ASP.NET thread pool (for diagnostics purpose, rather than an ultimate solution), to see if the described scenario is indeed the case here:
<configuration>
<system.web>
<applicationPool
maxConcurrentRequestsPerCPU="6000"
maxConcurrentThreadsPerCPU="0"
requestQueueLimit="6000" />
</system.web>
</configuration>
Updated to address the comments:
I realized I was missing a ContinueAwait somewhere in my chain. Now it
works fine when throwing an exception even when the top level doesn't
use ConfigureAwait(false).
This suggests that your code or a 3rd party library in use might be using blocking constructs (Task.Result
, Task.Wait
, WaitHandle.WaitOne
, perhaps with some added timeout logic). Have you looked for those? Try the Task.Run
suggestion from the bottom of this update. Besides, I'd still do the thread count diagnostics to rule out thread pool starvation/stuttering.
So are you saying that if I DO use ContinueAwait even at the top level
I lose the whole benefit of the async?
No, I'm not saying that. The whole point of async
is to avoid blocking threads while waiting for something, and that goal is achieved regardless of the added value of ContinueAwait(false)
.
What I'm saying is that not using ConfigureAwait(false)
might introduce redundant context switching (what usually means thread switching), which might be a problem in ASP.NET if thread pool is working at its capacity. Nevertheless, a redundant thread switch is still better than a blocked thread, in terms of the server scalability.
In all fairness, using ContinueAwait(false)
might also cause redundant context switching, especially if it's used inconsistently across the chain of calls.
That said, ContinueAwait(false)
is also often misused as a remedy against deadlocks caused by blocking on asynchronous code. That's why I suggested above to look for those blocking construct across all code base.
However the question still remains as posted as to whether or not
ConfigureAwait(false) should be used at the top level.
I hope Stephen Cleary could elaborate better on this, by here's my thoughts.
There's always some "super-top level" code that invokes your top-level code. E.g., in case of a UI app, it's the framework code which invokes an async void
event handler. In case of ASP.NET, it's the asynchronous controller's BeginExecute
. It is the responsibility of that super-top level code to make sure that, once your async task has completed, the continuations (if any) run on the correct synchronization context. It is not the responsibility of the code of your task. E.g., there might be no continuations at all, like with a fire-and-forget async void
event handler; why would you care to restore the context inside such handler?
Thus, inside your top-level methods, if you don't care about the context for await
continuations, do use ConfigureAwait(false)
as soon as you can.
Moreover, if you're using a 3rd party library which is known to be context agnostic but still might be using ConfigureAwait(false)
inconsistently, you may want to wrap the call with Task.Run
or something like WithNoContext
. You'd do that to get the chain of the async calls off the context, in advance:
var report = await Task.Run(() =>
_adapter.GetReportAsync()).ConfigureAwait(false);
return Json(report, JsonRequestBehavior.AllowGet);
This would introduce one extra thread switch, but might save you a lot more of those if ConfigureAwait(false)
is used inconsistently inside GetReportAsync
or any of its child calls. It'd also serve as a workaround for potential deadlocks caused by those blocking constructs inside the call chain (if any).
Note however, in ASP.NET HttpContext.Current
is not the only static property which is flowed with AspNetSynchronizationContext
. E.g., there's also Thread.CurrentThread.CurrentCulture
. Make sure you really don't care about loosing the context.
Updated to address the comment:
For brownie points, maybe you can explain the effects of
ConfigureAwait(false)... What context isn't preserved.. Is it just the
HttpContext or the local variables of the class object, etc.?
All local variables of an async
method are preserved across await
, as well as the implicit this
reference - by design. They actually gets captured into a compiler-generated async state machine structure, so technically they don't reside on the current thread's stack. In a way, it's similar to how a C# delegate captures local variables. In fact, an await
continuation callback is itself a delegate passed to ICriticalNotifyCompletion.UnsafeOnCompleted
(implemented by the object being awaited; for Task
, it's TaskAwaiter
; with ConfigureAwait
, it's ConfiguredTaskAwaitable
).
OTOH, most of the global state (static/TLS variables, static class properties) is not automatically flowed across awaits. What does get flowed depends on a particular synchronization context. In the absence of one (or when ConfigureAwait(false)
is used), the only global state preserved with is what gets flowed by ExecutionContext
. Microsoft's Stephen Toub has a great post on that: "ExecutionContext vs SynchronizationContext". He mentions SecurityContext
and Thread.CurrentPrincipal
, which is crucial for security. Other than that, I'm not aware of any officially documented and complete list of global state properties flowed by ExecutionContext
.
You could peek into ExecutionContext.Capture
source to learn more about what exactly gets flowed, but you shouldn't depend on this specific implementation. Instead, you can always create your own global state flow logic, using something like Stephen Cleary's AsyncLocal
(or .NET 4.6 AsyncLocal<T>
).
Or, to take it to the extreme, you could also ditch ContinueAwait
altogether and create a custom awaiter, e.g. like this ContinueOnScope
. That would allow to have precise control over what thread/context to continue on and what state to flow.
C# async/await chaining with ConfigureAwait(false)
Definitely not. ConfigureAwait
just as it's name suggest configures the await
. It only affects the await
coupled with it.
ConfigureAwait
actually returns a different awaitable type, ConfiguredTaskAwaitable
instead of Task
which in turn returns a different awaiter type ConfiguredTaskAwaiter
instead of TaskAwaiter
If you want to disregard the SynchronizationContext
for all your await
s you must use ConfigureAwait(false)
for each of them.
If you want to limit the use of ConfigureAwait(false)
you can use my NoSynchronizationContextScope
(see here) at the very top:
async Task CallerA()
{
using (NoSynchronizationContextScope.Enter())
{
await Method1Async();
}
}
Should we use ConfigureAwait(false) in libraries that call async callbacks?
When you say await task.ConfigureAwait(false)
you transition to the thread-pool causing mapping
to run under a null context as opposed to running under the previous context. That can cause different behavior. So if the caller wrote:
await Map(0, i => { myTextBox.Text = i.ToString(); return 0; }); //contrived...
Then this would crash under the following Map
implementation:
var result = await task.ConfigureAwait(false);
return await mapper(result);
But not here:
var result = await task/*.ConfigureAwait(false)*/;
...
Even more hideous:
var result = await task.ConfigureAwait(new Random().Next() % 2 == 0);
...
Flip a coin about the synchronization context! This looks funny but it is not as absurd as it seems. A more realistic example would be:
var result =
someConfigFlag ? await GetSomeValue<T>() :
await task.ConfigureAwait(false);
So depending on some external state the synchronization context that the rest of the method runs under can change.
This also can happen with very simple code such as:
await someTask.ConfigureAwait(false);
If someTask
is already completed at the point of awaiting it there will be no switch of context (this is good for performance reasons). If a switch is necessary then the rest of the method will resume on the thread pool.
This non-determinism a weakness of the design of await
. It's a trade-off in the name of performance.
The most vexing issue here is that when calling the API is is not clear what happens. This is confusing and causes bugs.
What to do?
Alternative 1: You can argue that it is best to ensure deterministic behavior by always using task.ConfigureAwait(false)
.
The lambda must make sure that it runs under the right context:
var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext;
Map(..., async x => await Task.Factory.StartNew(
() => { /*access UI*/ },
CancellationToken.None, TaskCreationOptions.None, uiScheduler));
It's probably best to hide some of this in a utility method.
Alternative 2: You can also argue that the Map
function should be agnostic to the synchronization context. It should just leave it alone. The context will then flow into the lambda. Of course, the mere presence of a synchronization context might alter the behavior of Map
(not in this particular case but in general). So Map
has to be designed to handle that.
Alternative 3: You can inject a boolean parameter into Map
that specifies whether to flow the context or not. That would make the behavior explicit. This is sound API design but it clutters the API. It seems inappropriate to concern a basic API such as Map
with synchronization context issues.
Which route to take? I think it depends on the concrete case. For example, if Map
is a UI helper function it makes sense to flow the context. If it is a library function (such as a retry helper) I'm not sure. I can see all alternatives make sense. Normally, it is recommended to apply ConfigureAwait(false)
in all library code. Should we make an exception in those cases where we call user callbacks? What if we have already left the right context e.g.:
void LibraryFunctionAsync(Func<Task> callback)
{
await SomethingAsync().ConfigureAwait(false); //Drops the context (non-deterministically)
await callback(); //Cannot flow context.
}
So unfortunately, there is no easy answer.
Static Analysis for ConfigureAwait
Any static analysis of this kind would be very difficult to implement correctly. You can use heuristics (e.g., UIElement
), but you'll probably end up with some false positives and/or false negatives.
For example, FlowDocument
does not derive from UIElement
. You could change your heuristic to test for DispatcherObject
-derived types, but then that would also include Freezable
, which may or may not require context - you can't always know at compile-time. So that's a guaranteed false positive (or negative) in the general case.
As another example, adding items to a collection that is exposed as a data-bound property would also require the context, even though the collection is not a UI element.
Similar problems exist in ASP.NET. HttpContext.Current
is obvious, but what about string formatting methods that implicitly use the current culture? There's a number of "gotchas" on the ASP.NET side, too.
That said, I do think it's a good idea; just be sure to have an easy way to ignore false positives and false negatives.
await/async is still confusing
ConfigureAwait is for the continuation of the await and where it should continue execution when the await is completed. It's not for which threadpool the awaitable runs on.
So when your await Task.Run()
completes, ConfigureAwait()
determines if the continuation should run on the Calling Context or the threadpool.
If you set it to ConfigureAwait(false)
then the finale log.Info
in your example would execute on the same threadpool as the previous two log.Info
's
In the article “Async and Await” by Stephen Cleary it is said that to
run awaitable on the thread pool you need to call
ConfigureAwait(false) on that awaitable.
You didn't give a link to the article but I would be highly surprised if Stephen Cleary got this wrong.
Can ConfigureAwait(false) in a library lose the synchronization context for the calling application?
No.
The capturing of the SynchronizationContext
happens on await
. ConfigureAwait
configures the specific await
.
If the application calls a library's async method and awaits it the SC is captured on the spot regardless of what happens inside the call.
Now, because the async method's synchronous part (which is the part before the first await
) is executed before a task is returned to be awaited, you can mess around with the SynchronizationContext
there, but ConfigureAwait
doesn't do that.
In your specific example you seem to be returning the result of ConfigureAwait
from the async method. That can't happen because ConfigureAwait
returns the ConfiguredTaskAwaitable
struct. If however we change the method return type:
public ConfiguredTaskAwaitable DoThingAsyc()
{
return otherLib.DoThingAsync().ConfigureAwait(false);
}
Then awaiting it will indeed affect the calling code's await behavior.
ConfigureAwait pushes the continuation to a pool thread
Why ConfigureAwait pro-actively pushes the await continuation to a pool thread here?
It doesn't "push it to a thread pool thread" as much as say "don't force myself to come back to the previous SynchronizationContext
".
If you don't capture the existing context, then the continuation which handles the code after that await
will just run on a thread pool thread instead, since there is no context to marshal back into.
Now, this is subtly different than "push to a thread pool", since there isn't a guarantee that it will run on a thread pool when you do ConfigureAwait(false)
. If you call:
await FooAsync().ConfigureAwait(false);
It is possible that FooAsync()
will execute synchronously, in which case, you will never leave the current context. In that case, ConfigureAwait(false)
has no real effect, since the state machine created by the await
feature will short circuit and just run directly.
If you want to see this in action, make an async method like so:
static Task FooAsync(bool runSync)
{
if (!runSync)
await Task.Delay(100);
}
If you call this like:
await FooAsync(true).ConfigureAwait(false);
You'll see that you stay on the main thread (provided that was the current context prior to the await), since there is no actual async code executing in the code path. The same call with FooAsync(false).ConfigureAwait(false);
will cause it to jump to thread pool thread after execution, however.
Task.Yield() in library needs ConfigureWait(false)
The exact equivalent of Task.Yield().ConfigureAwait(false)
(which doesn't exist since ConfigureAwait
is a method on Task
and Task.Yield
returns a custom awaitable) is simply using Task.Factory.StartNew
with CancellationToken.None
, TaskCreationOptions.PreferFairness
and TaskScheduler.Current
. In most cases however, Task.Run
(which uses the default TaskScheduler
) is close enough.
You can verify that by looking at the source for YieldAwaiter
and see that it uses ThreadPool.QueueUserWorkItem
/ThreadPool.UnsafeQueueUserWorkItem
when TaskScheduler.Current
is the default one (i.e. thread pool) and Task.Factory.StartNew
when it isn't.
You can however create your own awaitable (as I did) that mimics YieldAwaitable
but disregards the SynchronizationContext
:
async Task Run(int input)
{
await new NoContextYieldAwaitable();
// executed on a ThreadPool thread
}
public struct NoContextYieldAwaitable
{
public NoContextYieldAwaiter GetAwaiter() { return new NoContextYieldAwaiter(); }
public struct NoContextYieldAwaiter : INotifyCompletion
{
public bool IsCompleted { get { return false; } }
public void OnCompleted(Action continuation)
{
var scheduler = TaskScheduler.Current;
if (scheduler == TaskScheduler.Default)
{
ThreadPool.QueueUserWorkItem(RunAction, continuation);
}
else
{
Task.Factory.StartNew(continuation, CancellationToken.None, TaskCreationOptions.PreferFairness, scheduler);
}
}
public void GetResult() { }
private static void RunAction(object state) { ((Action)state)(); }
}
}
Note: I don't recommend actually using NoContextYieldAwaitable
, it's just an answer to your question. You should be using Task.Run
(or Task.Factory.StartNew
with a specific TaskScheduler
)
Related Topics
Ms Dynamics Crm Online 2011 - Authentication Issues
Equivalence of "With...End With" in C#
Expression.Lambda and Query Generation at Runtime, Simplest "Where" Example
ASP.NET Webapi: How to Perform a Multipart Post with File Upload Using Webapi Httpclient
Using Prepared Statement in C# with MySQL
C#: How to Get an Object by the Name Stored in String
Can't Change Gameobject Color via Script
Possible Unintended Reference Comparison
Converting String Expression to Integer Value Using C#
Why Does Visual Studio Type a Newly Minted Array as Nullable
Wrap C# Application in .Msi Installer
Selenium Stops When Browser Is Manually Interrupted
.Net - Convert Generic Collection to Datatable
Putting HTML Inside HTML.Actionlink(), Plus No Link Text
How to Increase Executiontimeout for a Long-Running Query
Iis Server & ASP.NET Core - 500.19 with Error Code 0X8007000D on Httpplatformhandler Tag