Контекст исполнения (ExecutionContext)
Если коротко - это некие "внешние данные", причем они подобны воздуху - мы его не замечаем пока его используем, но его отсутствие смерти подобно ! Обычно это данные по правам и доступам (Principal), данные аутентификации и т.д. и т.п. Сам же ExecutionContext - это просто контейнер для данных.
Причина его появления понятна и ясна - если в синхронном мире у нас есть только один поток и данные не изменны , то в асинхронном нам надо знать а где мы вообще в данный момент времени .
Мы можем в любой момент сохранить его:
// ambient state captured into ec ExecutionContext ec = ExecutionContext.Capture();
и восстановить:
ExecutionContext.Run(ec, delegate { … // code here will see ec’s state as ambient }, null);
Все методы (за исключением unsafe) в .NET Framework при асинхронных операциях используют этот механизм сохранения/восстановления контекста.
Например когда Task.Run запускает на исполнение делегат, то он копирует ExecutionContext и восстанавливает его в потоке делегата именно таким образом. Это справедливо в том числе для ThreadPool.QueueUserWorkItem, Delegate.BeginInvoke, Stream.BeginRead, DispatcherSynchronizationContext.Post .
Контекст синхронизации (SynchronizationContext)
Разработка софта любит абстракции ! Мы не так уж часто реализуем конкретный функционал, но довольно много пишем высокоуровневый код, который скрывает детали конкретной реализации. Это причина почему у нас есть абстрактные классы, виртуальные методы и т.д.
SynchronizationContext - это абстракция ! Абстракция которая олицетворяет то окружение в котором вы работаете. И как пример такого окружения можно взять приложение WinForms, у которого есть свой поток (UI thread) для контролов пользовательского интерфейса - и в случае если нам надо взаимодействовать с ними, мы используем Control.BeginInvoke метод который предоставляет WinForms .Мы передаем ему как параметр делегат , которому будет передано управление потоком, с которым ассоциирован этот контрол.
Итак - мы создали какую-то функцию ( делегат) которая успешно работает с WinForms. Но что если я захочу чтобы она работала и с WPF ? У WPF есть такой же поток (UI thread) для контролов пользовательского интерфейса, но есть одно НО - маршаллинг параметров отличается ! И вместо Control.BeginInvoke мы должны использовать Dispatcher.BeginInvoke (или InvokeAsync) !
То есть мы имеем ситуацию где два разных кода по сути делают одно и то же, или два разных API . И вот тут и был придуман SynchronizationContext ! У него есть метод Post - который по сути является надстройкой и в зависимости от того где он вызывается вызывает правильный метод:
мы можем написать код который будет работать В ЛЮБОМ UI !!!
ExecutionContext в отличие от SynchronizationContext копирует контекст окружения и восстанавливает его в текущий поток.
SynchronizationContext же в отличие от ExecutionContext также копирует контекст окружения, но использует его совершенно по другому ! Вместо восстановления он использует этот контекст для вызова переданного ему делегата. Способ и внутренний механизм вызова этого делегата, а также использование скопированного контекста полностью зависят от внутренней реализации SynchronizationContext.Post.
Ответ - да, является. Если вызвать ExecutionContext.Capture() - то SynchronizationContext будет внутри ( если он конечно существует).
Итак - мы создали какую-то функцию ( делегат) которая успешно работает с WinForms. Но что если я захочу чтобы она работала и с WPF ? У WPF есть такой же поток (UI thread) для контролов пользовательского интерфейса, но есть одно НО - маршаллинг параметров отличается ! И вместо Control.BeginInvoke мы должны использовать Dispatcher.BeginInvoke (или InvokeAsync) !
То есть мы имеем ситуацию где два разных кода по сути делают одно и то же, или два разных API . И вот тут и был придуман SynchronizationContext ! У него есть метод Post - который по сути является надстройкой и в зависимости от того где он вызывается вызывает правильный метод:
- для WinForms вызывается Control.BeginInvoke
- для WPF вызывается Dispatcher.BeginInvoke
- если вдруг появится еще платформа для UI - будет вызываться правильный метод
Согласен - очень похоже на банальный "костыль" ! Но - "такова реальность данная нам в ощущениях" !
Таким образом вместо кода который работает только в WinForms UI :
public static void DoWork(Control c) { ThreadPool.QueueUserWorkItem(delegate { … // do work on ThreadPool c.BeginInvoke(delegate { … // do work on UI }); }); }
мы можем написать код который будет работать В ЛЮБОМ UI !!!
public static void DoWork() { var sc = SynchronizationContext.Current; ThreadPool.QueueUserWorkItem(delegate { … // do work on ThreadPool sc.Post(delegate { … // do work on the original context }, null); }); }
И в чем же разница ?
Несмотря на очевидную схожесть она принципиальна и огромна !ExecutionContext в отличие от SynchronizationContext копирует контекст окружения и восстанавливает его в текущий поток.
SynchronizationContext же в отличие от ExecutionContext также копирует контекст окружения, но использует его совершенно по другому ! Вместо восстановления он использует этот контекст для вызова переданного ему делегата. Способ и внутренний механизм вызова этого делегата, а также использование скопированного контекста полностью зависят от внутренней реализации SynchronizationContext.Post.
А разве SynchronizationContext не является частью ExecutionContext ?
Ответ - да, является. Если вызвать ExecutionContext.Capture() - то SynchronizationContext будет внутри ( если он конечно существует).
Супер,спс.
ОтветитьУдалить