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
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 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);
}