I\'m getting confusing behavior when using a different SynchronizationContext inside an async function than outside.
Most of my program\'s code uses a custom Synchro
I would expect your code to work, but there are a few possible reasons why it's not:
SynchronizationContext
is current when it executes its continuations.SynchronizationContext
is captured.SynchronizationContext
is to establish the current one in one method, and then run another (possibly-asynchronous) method that depends on it.SynchronizationContext
is to append ConfigureAwait(false)
to all tasks that are awaited.There is a common misconception about await
, that somehow calling an async
-implemented function is treated specially.
However, the await
keyword operates on an object, it does not care at all where the awaitable object comes from.
That is, you can always rewrite await Blah();
with var blahTask = Blah(); await blahTask;
So what happens when you rewrite the outer await
call that way?
// Synchronization Context leads to main thread;
Task<int> xTask = SomeOtherFunction();
// Synchronization Context has already been set
// to null by SomeOtherFunction!
int x = await xTask;
And then, there is the other issue: The finally
from the inner method is executed in the continuation, meaning that it is executed on the thread pool - so not only you have unset your SynchronizationContext
, but your SynchronizationContext
will (potentially) be restored at some time in the future, on another thread. However, because I do not really understand the way that the SynchronizationContext
is flowed, it is quite possible that the SynchronizationContext
is not restored at all, that it is simply set on another thread (remember that SynchronizationContext.Current
is thread-local...)
These two issues, combined, would easily explain the randomness that you observe. (That is, you are manipulating quasi-global state from multiple threads...)
The root of the issue is that the await
keyword does not allow scheduling of the continuation task.
In general, you simply want to specify "It is not important for the code after the await
to be on the same context as the code before await
", and in that case, using ConfigureAwait(false)
would be appropriate;
async Task SomeOtherFunction() {
await Blah().ConfigureAwait(false);
}
However, if you absolutely want to specify "I want the code after the await
to run on the thread pool" - which is something that should be rare, then you cannot do it with await
, but you can do it e.g. with ContinueWith
- however, you are going to mix multiple ways of using Task
objects, and that can lead to pretty confusing code.
Task SomeOtherFunction() {
return Blah()
.ContinueWith(blahTask => WeShouldBeInAThreadPoolThread(),
TaskScheduler.Default);
}