Complete and working extension methods for getting the SynchronizationContext
from a Thread
or ExecutionContext
(or null
if none is present), or a DispatcherSynchronizationContext
from a Dispatcher
. Tested on .NET 4.6.2.
using Ectx = ExecutionContext;
using Sctx = SynchronizationContext;
using Dctx = DispatcherSynchronizationContext;
public static class _ext
{
// DispatcherSynchronizationContext from Dispatcher
public static Dctx GetSyncCtx(this Dispatcher d) => d?.Thread.GetSyncCtx() as Dctx;
// SynchronizationContext from Thread
public static Sctx GetSyncCtx(this Thread th) => th?.ExecutionContext?.GetSyncCtx();
// SynchronizationContext from ExecutionContext
public static Sctx GetSyncCtx(this Ectx x) => __get(x);
/* ... continued below ... */
}
All of the above functions end up calling the __get
code shown below, which warrants some explanation.
Note that __get
is a static field, pre-initialized with a discardable lambda block. This allows us to neatly intercept the first caller only, in order to run the one-time initialization, which prepares a tiny and permanent replacement delegate that's much faster and reflection-free.
The final act for the intrepid initialization effort is to swap the replacement into '__get', which simultaneously and tragically means the code discards itself, leaving no trace, and all subsequent callers proceed directly into the DynamicMethod
proper without even a hint of bypass logic.
static Func<Ectx, Sctx> __get = arg =>
{
// Hijack the first caller to do initialization...
var fi = typeof(Ectx).GetField(
"_syncContext", // private field in 'ExecutionContext'
BindingFlags.NonPublic|BindingFlags.Instance);
var dm = new DynamicMethod(
"foo", // (any name)
typeof(Sctx), // getter return type
new[] { typeof(Ectx) }, // type of getter's single arg
typeof(Ectx), // "owner" type
true); // allow private field access
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fi);
il.Emit(OpCodes.Ret);
// ...now replace ourself...
__get = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
// oh yeah, don't forget to handle the first caller's request
return __get(arg); // ...never to come back here again. SAD!
};
The cute part is the very end where--in order to actually fetch the value for the pre-empted first caller--the function ostensibly calls itself with its own argument, but avoids recursing by replacing itself immediately prior.
There's no particular reason for demonstrating this unusual technique on the particular problem of SynchronizationContext
under discussion on this page. Grabbing the _syncContext
field out of an ExecutionContext
could be easily and trivially addressed with traditional reflection (plus some extension method frosting). But I thought I'd share this approach which I've personally used for quite a while now, because it is also easily adapted and just as widely applicable to such cases.
It's especially appropriate when extreme performance is necessary in accessing the non-public field. I think I originally used this in a QPC-based frequency counter where the field was read in a tight loop that iterated every 20 or 25 nanoseconds, which wouldn't really be possible with conventional reflection.
This concludes the main answer, but below I've included some interesting points, less relevant to the questioner's inquiry, moreso to the technique just demonstrated.
Runtime callers
For clarity, I separated the "installation swap" and "first usage" steps into two separate lines in the code shown above, as opposed to what I have in my own code (the following version also avoids one main-memory fetch versus the previous, potentially implicating thread-safety, see detailed discussion below):
return (__get = (Func<Ectx, Sctx>)dm.CreateDel...(...))(arg);
In other words, all callers, including the first, fetch the value in exactly the same way, and no reflection code is ever used to do so. It only writes the replacement getter. Courtesy of il-visualizer, we can see the body of that DynamicMethod
in the debugger at runtime:
Lock-free thread safety
I should note that swapping in the function body is a fully thread-safe operation given the .NET memory model and the lock-free philosophy. The latter favors forward-progress guarantees at the possible expense of doing duplicate or redundant work. Multi-way racing to initialize is correctly permitted on a fully sound theoretical basis:
- the race entry point (initialization code) is globally pre-configured and protected (by the .NET loader) so that (multiple) racers (if any) enter the same initializer, which can never be seen as
null
.
- multiple race products (the getter) are always logically identical, so it doesn't matter which one any particular racer (or later non-racing caller) happens to pick up, or even whether any racer ends up using the one they themselves produced;
- each installation swap is a single store of size
IntPtr
, which is guaranteed to be atomic for any respective platform bitness;
- finally, and technically absolutely critical to perfect formal correctness, work products of the "losers" are reclaimed by
GC
and thus do no leak. In this type of race, losers are every racer except the last finisher (since everyone else's efforts get blithely and summarily overwritten with an identical result).
Although I believe these points combine to fully protect the code as written under every possible circumstance, if you're still suspicious or wary of the overall conclusion, you can always add an extra layer of bulletproofing:
var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
Thread.MemoryBarrier();
__get = tmp;
return tmp(arg);
It's just a paraniod version. As with the earlier condensed one-liner, the .NET memory model guarantees that there is exactly one store--and zero fetches--to location of '__get'. (The full expanded example at the top does do an extra main memory fetch, but is still sound thanks to the second bullet point) As I mentioned, none of this should be necessary for correctness, but it could, in theory, give a miniscule performance bonus: by conclusively ending the race earlier, the aggressive flush could, in an extremely rare case, prevent a subsequent caller on a dirty cache line from unnecessarily (but again, harmlessly) racing.
Double-thunking
Calls into the final, hyper-fast method are still thunked through the static extension methods shown earlier.
This is because we also need to somehow represent entry point(s) that actually exist at compile time for the compiler to bind against and propagate metadata for. The double-thunk is a small price to pay for the overwhelming convenience of strongly-typed metadata and intellisense in the IDE for customized code that can't actually be resolved until runtime. Yet it runs at least as fast as statically compiled code, way faster that doing a bunch of reflection on every call, so we get the best of both worlds!
var context = (SynchronizationContext)someUiControl.Invoke(new Func<SynchronizationContext>(() => SynchronizationContext.Current));
and cache it for later use. – Commence