Полных и разработкой методов расширения для получения SynchronizationContext
из Thread
или ExecutionContext
(или null
, если никто не присутствует), или DispatcherSynchronizationContext
из Dispatcher
. Проверено на .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 ... */
}
Все вышеперечисленные функции в конечном итоге вызова __get
код как показано ниже, что требует некоторых объяснений.
Обратите внимание, что __get
- это статическое поле, предварительно инициализированное отбрасываемым лямбда-блоком. Это позволяет нам аккуратно перехватить первого вызывающего абонента только, чтобы запустить одноразовую инициализацию, которая подготавливает крошечный и постоянный заменяющий делегат, который намного быстрее и без отражений.
Заключительный акт для неустранимого усилия по инициализации заключается в замене замены на «__get», что одновременно и трагически означает, что код отбрасывает себя, не оставляя следов, и все последующие вызывающие лица непосредственно направляются непосредственно в DynamicMethod
, даже без подсказки логики байпаса.
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!
};
Мило часть самого конца, где - для того, чтобы фактически получить значение для Вытеснено первого вызывающего абонента - функция якобы называет себя со своим собственным аргументом, но избегает рекурсии, заменив себя сразу до.
Нет особой причины для демонстрации этой необычной техники по конкретной проблеме SynchronizationContext
, обсуждаемой на этой странице. Захват поля _syncContext
из ExecutionContext
может быть легко и тривиально адресован традиционным отражением (плюс некоторый метод замораживания расширения). Но я думал, что поделюсь этим подходом, который я лично использовал уже довольно давно, потому что он также легко адаптируется и так же широко применим к таким случаям.
Это особенно удобно, когда требуется высокая производительность при доступе к непубличному полю. Я думаю, что я изначально использовал это на частотном счетчике на основе QPC, где поле было прочитано в узком цикле, который повторял каждые 20 или 25 наносекунд, что было бы невозможно при обычном отражении.
На этом мы заканчиваем основной ответ, но ниже я включил некоторые интересные моменты, менее релевантные запросу исследователя, более похожим на только что продемонстрированную технику.
среды выполнения вызывающие абоненты
Для ясности, я отделил «установки своп» и «первого использования» шагов на две отдельные строки в коде, показанных выше, в отличие от того, что У меня в моем собственном коде (в следующей версии также избегается выбор одной основной памяти по сравнению с предыдущей, потенциально подразумевающей безопасность потока), см. Подробное обсуждение ниже):
return (__get = (Func<Ectx, Sctx>)dm.CreateDel...(...))(arg);
Другими словами, все вызывающие абоненты, , включая первый, выбирают значение точно так же, и код отражения никогда не используется для этого. Он записывает только запись getter. Предоставлено il-visualizer, мы можем увидеть тело этого DynamicMethod
в отладчике во время выполнения:
безблокировочной потокобезопасность
Я хотел бы отметить, что замена в теле функции является полностью потокобезопасной работы с учетом NET memory model и философии блокировки. Последнее способствует гарантиям прогресса вперед при возможном расходовании дублирующей или избыточной работы. Многоходовой гонки инициализировать правильно разрешено на полностью солидной теоретической базе:
- точка входа гонки (код инициализации) глобально предварительно сконфигурированной и защищенной (в .NET загрузчиком), так что (несколько) Гонщики (если есть), введите тот же инициализатор, который никогда не может рассматриваться как
null
.
- несколько гоночных продуктов (геттер) всегда логически идентичны, поэтому не имеет значения, какой именно гонщик (или более поздний не-гоночный звонящий) может подобрать или даже выиграет ли какой-либо гонщик, используя тот, который они сами производились;
- каждый установочный своп представляет собой единый магазин размером
IntPtr
, который гарантированно будет атомарным для любой соответствующей битты платформы;
- окончательно и технически абсолютно критический для идеальной формальной корректности, рабочие продукты «проигравших» возвращаются
GC
и, следовательно, не пропускают утечку. В this type of race проигравшими являются каждый гонщик, кроме последний финишер (так как усилия всех остальных становятся беспечными и суммарно перезаписываются с одинаковым результатом).
Хотя я считаю, что эти точки объединяются, чтобы полностью защитить код, написанный под любым возможным обстоятельством, если вы все еще с подозрением или с осторожностью относиться к общему выводу, что вы всегда можете добавить дополнительный слой Буллетпроофинг:
var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
Thread.MemoryBarrier();
__get = tmp;
return tmp(arg);
Это просто параноидная версия. Как и в случае с более ранним конденсированным одним слоем, модель памяти .NET гарантирует, что существует ровно один магазин - и ноль выборки - до местоположения '__get'. (Полный расширенный пример в верхней части делает дополнительную выборку основной памяти, но по-прежнему звучит благодаря второй маркерной точке). Как я уже упоминал, ни одно из этого не должно быть необходимым для правильности, но теоретически это может дать минимальное бонус производительности: окончательно закончив гонку ранее, агрессивный флеш мог в чрезвычайно редком случае предотвратить последующий вызов на грязной линии кеша из-за ненужного (но опять же безвредного) гонок.
Дважды thunking
вызовы в финал, сверхскоростной метод все еще thunked через статические методы расширения, показанных ранее. Это связано с тем, что нам также необходимо каким-то образом представить точки входа, которые на самом деле существуют во время компиляции, для компилятора для привязки и распространения метаданных. Double-Thunk - это небольшая цена, чтобы заплатить за подавляющее удобство сильно типизированных метаданных и intellisense в среде IDE для настроенного кода, который фактически не может быть разрешен до выполнения. Тем не менее он работает как минимум так же быстро, как статически скомпилированный код: way быстрее, что делает кучу отражения на каждом звонке, поэтому мы получаем лучшее из обоих миров!
В более позднее время (после загрузки реализации WinForms) вы могли бы получить контекст синхронизации потока пользовательского интерфейса следующим образом: (untested) 'var context = (SynchronizationContext) someUiControl.Invoke (new Func (() = > SynchronizationContext.Current)); 'и кешировать его для последующего использования. –
Heinzi
@Heinzi: Выглядит творчески. Однако мне нужен элемент управления для этого, что еще хуже, чем необходимость в объекте SynchronizationContext. – chiccodoro
Не знаю почему, но моя ссылка на этот вопрос у меня [«Является ли фраза из книги». Текущий SynchronizationContext является свойством текущего потока «правильно»? »(Http://stackoverflow.com/questions/16296369/is -the-phrase-from-a-book-the-current-synchronizationcontext-is-a-property-of-t) не отображается в разделе Связанные или Связанные на правой боковой панели, поэтому я помещал его в этот комментарий ... поэтому он появился после этого –