There are a couple of things (but 1 main thing) that I don\'t understand about the behavior of the following code.
Can someone help explain this?
It\'s actu
2.2
Is quite simple to explain, 1.2
not as easy.
The reason 2.2
prints null
is due to when you await
using the default (new SynchronizationContext
) or null
SynchronizationContext, the Post
method will get called passing in the continuation delegate, this is scheduled on the ThreadPool. It makes no effort to restore the current instance, it relies on the current SynchronizationContext
being null
for these continuations when they run on the ThreadPool (which it is). To be clear, because you are not using .ConfigureAwait(false)
your continuation will get posted to the captured context as you are expecting, but the Post
method in this implementation doesn't preserve/flow the same instance.
To fix this (i.e. make your context "sticky"), you could inherit from SynchronizationContext
, and overload the Post
method to call SynchronizationContext.SetSynchronizationContext(this)
with the posted delegate (using Delegate.Combine(...)
). Also, the internals treat SynchronizationContext
instances the same as null
in most places, so if you want to play with this stuff, always create an inheriting implementation.
For 1.2
, this actually surprised me also, as my understanding was that this would call the underlying state machine (along with all the internals from AsyncMethodBuilder
), but it would be called synchronously while maintaining its SynchronizationContext
.
I think what we are seeing here is explained in this post, and it's to do with ExecutionContext being captured and restored inside of the AsyncMethodBuilder
/ async state machine, this is protecting and preserving the calling ExecutionContext
and hence SynchronizationContext
. Code for this can been seen here (thanks @VMAtm).