2010-11-05 6 views
7

Я, кажется, не найти, как получить SynchronizationContext данного Thread:Получить SynchronizationContext из данной Thread

Thread uiThread = UIConfiguration.UIThread; 
SynchronizationContext context = uiThread.Huh?; 

Зачем мне это?

Потому что мне нужно отправить сообщение в UIThread из разных мест по всему интерфейсу. Поэтому я определил статическое свойство в классе под названием UIConfiguration. Я установить это свойство в Program.Main метода:

UIConfiguration.UIThread = Thread.CurrentThread; 

В тот самый момент я могу быть уверен, что я имею право нить, но я не могу установить статическое свойство, как

UIConfiguration.SynchronizationContext = SynchronizationContext.Current 

, потому что реализация WinForms из этот класс еще не установлен. Поскольку каждый поток имеет свой собственный SynchronizationContext, он должен быть способен извлечь его из заданного объекта Thread, или я совершенно не прав?

+0

В более позднее время (после загрузки реализации WinForms) вы могли бы получить контекст синхронизации потока пользовательского интерфейса следующим образом: (untested) 'var context = (SynchronizationContext) someUiControl.Invoke (new Func (() = > SynchronizationContext.Current)); 'и кешировать его для последующего использования. – Heinzi

+0

@Heinzi: Выглядит творчески. Однако мне нужен элемент управления для этого, что еще хуже, чем необходимость в объекте SynchronizationContext. – chiccodoro

+0

Не знаю почему, но моя ссылка на этот вопрос у меня [«Является ли фраза из книги». Текущий SynchronizationContext является свойством текущего потока «правильно»? »(Http://stackoverflow.com/questions/16296369/is -the-phrase-from-a-book-the-current-synchronizationcontext-is-a-property-of-t) не отображается в разделе Связанные или Связанные на правой боковой панели, поэтому я помещал его в этот комментарий ... поэтому он появился после этого –

ответ

12

Это невозможно. Проблема в том, что SynchronizationContext и Thread действительно две совершенно разные концепции.

Хотя верно, что Windows Forms и WPF устанавливают SynchronizationContext для основного потока, большинство других потоков нет. Например, ни один из потоков ThreadPool не содержит собственный SynchronizationContext (если, конечно, вы не устанавливаете свой собственный).

Также возможно, что SynchronizationContext будет , полностью не связанный с резьбой и резьбой. Контекст синхронизации может быть легко настроен на синхронизацию с внешней службой или с полным пулом потоков и т. Д.

В вашем случае я бы рекомендовал установить UIConfiguration.SynchronizationContext в начальном, загруженном событии основной формы. Контекст гарантированно будет запущен в этот момент и будет непригодным до тех пор, пока в любом случае не будет запущен насос сообщений.

+0

Что касается недостающего свойства someThread.SynchronizationContext, все равно было бы полезно, если бы объект Thread предоставил это свойство. понятие «что бы« SynchronizationContext.Current »дал мне, если бы я был тем определенным потоком? Для потоков, которые не имеют контекста, он вернул бы« нуль »так же, как и SynchronizationContext.Current. – chiccodoro

+0

Кроме того, спасибо для информационного ответа. Как и Джон, и вы предположили, что я поместил это в обработчик события «Load». – chiccodoro

+0

Рид, я спросил подзапрос [«Является ли фраза из книги« Текущий SynchronizationContext является свойством текущего потока » ct "?"] (http://stackoverflow.com/questions/16296369/is-the-phrase-from-a-book-the-current-synchronizationcontext-is-a-property-of-t) –

2

Я не верю, что каждый поток делает иметь свой собственный SynchronizationContext - это просто имеет внутрипотоковую SynchronizationContext.

Почему бы вам не задать UIConfiguration.UIThread в мероприятии Loaded, если ваша форма, или что-то подобное?

+0

Это было не совсем удовлетворено тем, что эта строка кода «в середине кода» - я использую Application.Run() дважды, сначала для заставки, а затем для основной формы , поэтому теперь он помещается в событие загрузки заставки, но на самом деле: пока он там, он работает.Если бы экран заставки когда-либо удалялся, я сразу заметил бы это, потому что свойство поднимет «NullPointerException». – chiccodoro

+0

Джон, я спросил о последующих действиях [«Является ли фраза из книги». Текущий SynchronizationContext является свойством текущего потока «правильно»? »(Http://stackoverflow.com/questions/16296369/is -the-phrase-from-a-book-the-current-synchronizationcontext-is-a-property-of-t) –

7

Я знаю, что это старый вопрос, и извиниться за некро, но я нашел решение этой проблемы, которое, как я полагал, может быть полезно для тех из нас, кто придумал это (и это не требует Контрольный экземпляр).

В принципе, вы можете создать экземпляр WindowsFormsSynchronizationContext и установить контекст вручную в вашей Main функции, например, так:

_UISyncContext = new WindowsFormsSynchronizationContext(); 
    SynchronizationContext.SetSynchronizationContext(_UISyncContext); 

Я делал это в моем приложении, и она прекрасно работает без проблем. Тем не менее, я должен указать, что мой Main отмечен STAThread, поэтому я не уверен, будет ли это работать (или если это даже необходимо), если ваш Main отмечен вместо MTAThread.

EDIT: Я забыл упомянуть об этом, ноуже определен на уровне модуля в классе Program в моем приложении.

4

я нашел, как наиболее кратким и полезным для меня следующие отрывки из книги Алекса Дэвиса "Async в C# 5.0 O'Reilly публ, 2012..", P.48-49:

  • «SynchronizationContext is a class provided by the .NET Framework, который имеет возможность запускать код в определенного типа потока.
    Существуют различные контексты синхронизации, используемые .NET, наиболее важными из которых являются контексты потоков пользовательского интерфейса, используемые WinForms и WPF».

  • «Экземпляры SynchronizationContext сам ничего очень полезно, не сделать это все фактические примеры него, как правило, подклассы.

    Он также имеет статические элементы, которые позволяют читать и контролировать текущую SynchronizationContext.

    ток SynchronizationContext это свойство текущего потока.

    идея заключается в том, что в любой момент, что вы работаете в специальной теме, вы должны быть в состоянии получить текущий SynchronizationContext и сохраните его. Позже вы можете использовать его для запуска кода в специальном потоке, который вы начали. Все это должно быть возможно , без необходимости точно знать, с какого потока вы начали работать, пока вы можете использовать SynchronizationContext, вы можете вернуться к нему.

    Важным методом SynchronizationContext является Post, который может сделать делегата работать в правильном контексте «
    .

  • » Some SynchronizationContexts encapsulate a single thread, like the UI thread.
    Некоторые инкапсулируют конкретный вид нитки - например, пул потоков - но может выбрать любой из этих потоков , чтобы отправить делегата в. Некоторые из них на самом деле не изменятся, какой поток код работает, но используются только для мониторинга, как контекст синхронизации ASP.NET»

+0

+1 Спасибо , Хотя он не отвечает на мой первоначальный вопрос, ваш ответ по-прежнему добавляет ценную информацию. – chiccodoro

1

Полных и разработкой методов расширения для получения 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 в отладчике во время выполнения:

ldarg.0<br>ldfld SynchronizationContext _syncContext/ExecutionContext<br>ret

безблокировочной потокобезопасность

Я хотел бы отметить, что замена в теле функции является полностью потокобезопасной работы с учетом 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 быстрее, что делает кучу отражения на каждом звонке, поэтому мы получаем лучшее из обоих миров!

+0

В основном вы говорите, что поток имеет контекст выполнения, и у него есть частное поле, которое является контекстом синхронизации, и к нему можно получить доступ путем отражения. Оказывается, это так. Однако неверно, что этот контекст синхронизации запускается обратно в поток, из которого вы его извлекли, и это свойство было необходимо. Таким образом, не только ваш ответ очень трудно расшифровать, но и неверно. Извините за звучание немного arsey, но я хотел уточнить мой downvote. – briantyler

+0

@briantyler Код работает так, как рекламируется, чтобы получить 'SynchronizationContext' для' Thread', указанный вызывающим, если он имеет один (и критически на WPF, не требует молчания, если это не так, что является реальным использованием Вот). Это правда, что операция несколько пустая, если вызывающий указывает «Thread.CurrentThread». –

+0

Дело в том, что вы получаете «SynchronizationContext», но на самом деле вы не получаете его для потока, указанного для вызывающего. Этот контекст не ссылается на указанный поток, который вы хотите, чтобы он делал, поэтому он меньше, чем бесполезно. – briantyler

Смежные вопросы