суббота, 4 июня 2016 г.

Контексты исполнения (ExecutionContext) и синхронизации (SynchronizationContext) - что это такое и с чем их едят ?

Контекст исполнения (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 - который по сути является надстройкой и в зависимости от того где он вызывается вызывает правильный метод:

  1. для WinForms вызывается Control.BeginInvoke
  2. для WPF вызывается Dispatcher.BeginInvoke
  3. если вдруг появится еще платформа для 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 будет внутри ( если он конечно существует).

А как же ASP.NET и его HttpContext

Да - в ASP.NET также используется контекст синхронизации, однако он помещает делегат не в очередь обработки сообщений окна, а в пул ASP.NET.


1 комментарий :