Revisiting Task.Configureawait(Continueoncapturedcontext: False)

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 ThreadPoolstarvation 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 awaits 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



Leave a reply



Submit