Disable capture context in all library code, ConfigureAwait (false)

When using await , the SynchronizationContext is written by default (if one exists), and the code blocks after await (continuation blocks) are executed using this context (which causes the context of the stream to switch).

 public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await DoSomethingElseAsync(); // We are back on the same thread as before here //(well sometimes, depending on how the captured SynchronizationContext is implemented) } 

Although this default value may make sense in the context of the user interface in which you want to return to the user interface after the asynchronous operation is completed, for most other scenarios this does not make sense. This, of course, does not make sense for the internal code of the library, because

  • It comes with the overhead of unnecessary stream context switches.
  • It is very easy to accidentally create deadlocks (as described here or here ).

It seems to me that Microsoft made the decision about the wrong default.

Now my question is:

Is there any other (preferably better) way to solve this than cluttering all await calls in my code with .ConfigureAwait(false) ? It is so easy to forget and makes the code less readable.

Update: Maybe await Task.Yield().ConfigureAwait(false); at the beginning of each method? If this guaranteed me that I would be in a stream without SynchronizationContext attacks, all subsequent await calls would not capture any context.

+7
c # async-await
source share
2 answers

To start, await Task.Yield().ConfigureAwait(false) will not work because Yield does not return Task . There are other ways to go to the pool stream, but using them is also not recommended, check "Why is SwitchTo removed from Async CTP / Release?"

If you still want to do this, here is a good trick using the fact that ConfigureAwait(false) pushes the continuation to the pool thread if there is synchronization in the original thread, although there is no asynchrony here:

 static Task SwitchAsync() { if (SynchronizationContext.Current == null) return Task.FromResult(false); // optimize var tcs = new TaskCompletionSource<bool>(); Func<Task> yield = async () => await tcs.Task.ConfigureAwait(false); var task = yield(); tcs.SetResult(false); return task; } // ... public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await SwitchAsync().ConfigureAwait(false); // We're on a thread pool thread without a SynchronizationContext await DoSomethingElseAsync(); // no need for ConfigureAwait(false) here // ... } 

Again, this is not something that I widely use myself. I had similar problems about using ConfigureAwait(false) . One of the results was, while ConfigureAwait(false) might not be completely perfect, using it with await once you don't care about the synchronization context is the way to go. In this guide, the .NET source code itself follows.

Another result: if you are worried about third-party code inside DoSomethingElseAsync that might not use ConfigureAwait(false) correctly, just follow these steps:

 public async Task DoSomethingAsync() { // We are on a thread that has a SynchronizationContext here. await Task.Run(() => DoSomethingElseAsync()).ConfigureAwait(false); // We're on a thread pool thread without a SynchronizationContext await DoYetSomethingElseAsync(); // no need for ConfigureAwait(false) here // ... } 

This will use the Task.Run override, which accepts the Func<Task> lambda, runs it in the pool thread, and returns the expanded task. You would only do this for the first await inside DoSomethingAsync . The potential cost is the same as for SwitchAsync : one additional stream switch, but the code is more readable and more structured. This is the approach that I use in my work.

+2
source share

Is there a better way to solve this than cluttering up all the call waiting in my code with .ConfigureAwait (false)? It is so easy to forget and makes the code less readable.

Not really. There is no out of the box switch that you can turn on to change this behavior. There is a ConfigureAwaiter Checker ReSharper Extension that can help. Another alternative would be to use a custom extension method or SynchronizationContext wrapper, which switches the context or, alternatively, even a custom awaiter.

+1
source share

All Articles