Что делает SynchronizationContext?

В книге "Программирование С#" у него есть пример кода SynchronizationContext:

SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
    string text = File.ReadAllText(@"c:\temp\log.txt");
    originalContext.Post(delegate {
        myTextBox.Text = text;
    }, null);
});

Я новичок в потоках, поэтому, пожалуйста, ответьте подробно. Во-первых, я не знаю, что означает контекст, что сохраняет программа в originalContext? И когда запущен метод Post, что будет делать поток пользовательского интерфейса?
Если я попрошу некоторые глупые вещи, пожалуйста, исправьте меня, спасибо!

EDIT: Например, что, если я просто напишу myTextBox.Text = text; в методе, какая разница?

Ответ 1

Что делает SynchronizationContext?

Проще говоря, Post будет вызываться в этом месте. (Post - это неблокирующая/асинхронная версия Send.)

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

Несмотря на то, что я только что написал (каждый поток имеет связанный контекст синхронизации), SynchronizationContext необязательно представляет конкретный поток; он также может перенаправлять вызовы делегатов, переданных ему, на любой из нескольких потоков (например, на ThreadPool рабочий поток) или (по крайней мере теоретически) на конкретное ядро ​​ЦП или даже на другой сетевой узел. Там, где ваши делегаты заканчиваются, зависит от типа SynchronizationContext.

Windows Forms установит WindowsFormsSynchronizationContext в поток, на котором создается первая форма. (Этот поток обычно называется "потоком пользовательского интерфейса".) Этот тип контекста синхронизации вызывает делегированных ему делегатов именно в этом потоке. Это очень полезно, так как Windows Forms, как и многие другие интерфейсы пользовательского интерфейса, разрешает манипулирование элементами управления в том же потоке, на котором они были созданы.

Что делать, если я просто пишу myTextBox.Text = text; в методе, какая разница?

Код, который вы передали myTextBox) до этого конкретного назначения. Это делается следующим образом:

  • Пока вы по-прежнему находитесь в потоке пользовательского интерфейса, запишите Windows Forms SynchronizationContext и сохраните ссылку на него в переменной (originalContext) для дальнейшего использования. Вы должны запросить SynchronizationContext.Current в этот момент; если вы запросили его в коде, переданном в ThreadPool.QueueUserWorkItem, вы можете получить любой контекст синхронизации, связанный с потоком рабочего потока пула потоков. После того, как вы сохранили ссылку на контекст Windows Forms, вы можете использовать его в любом месте и в любое время, чтобы "отправить" код в поток пользовательского интерфейса.

  • Всякий раз, когда вам нужно манипулировать элементом пользовательского интерфейса (но не более того, а может и не быть) в потоке пользовательского интерфейса, обратитесь к контексту синхронизации Windows Forms через originalContext и передайте код, который будет манипулировать пользовательский интерфейс либо Send, либо Post.


Заключительные замечания и советы:

  • Контексты синхронизации для вас не говорят о том, какой код должен выполняться в определенном месте/контексте, и какой код можно просто выполнить нормально, не передавая его в SynchronizationContext. Чтобы решить это, вы должны знать правила и требования к структуре, которую вы программируете против — Windows Forms в этом случае.

    Итак, помните это простое правило для Windows Forms: НЕ ИСПОЛЬЗУЙТЕ элементы управления или формы из потока, отличного от того, который их создал. Если вы должны это сделать, используйте механизм SynchronizationContext, как описано выше, или async/await ключевые слова и Параллельная библиотека задач (TPL), то есть API, окружающая

Ответ 2

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

Если вы знакомы с моделью программирования Win32, очень близкой аналогией будет API PostMessage и SendMessage, который вы можете вызвать для отправки сообщения из потока, отличного от целевого окна.

Вот очень хорошее объяснение того, какие контексты синхронизации: Все о SynchronizationContext.

Ответ 3

В нем хранится поставщик синхронизации, класс, полученный из SynchronizationContext. В этом случае это, вероятно, будет экземпляр WindowsFormsSynchronizationContext. Этот класс использует методы Control.Invoke() и Control.BeginInvoke() для реализации методов Send() и Post(). Или это может быть DispatcherSynchronizationContext, он использует Dispatcher.Invoke() и BeginInvoke(). В приложении Winforms или WPF этот провайдер автоматически устанавливается сразу после создания окна.

Когда вы запускаете код в другом потоке, например поток потока пулов, используемый во фрагменте, тогда вы должны быть осторожны, чтобы напрямую не использовать объекты, которые являются небезопасными. Как и любой объект пользовательского интерфейса, вы должны обновить свойство TextBox.Text из потока, создавшего TextBox. Метод Post() гарантирует, что цель делегирования будет выполняться в этом потоке.

Остерегайтесь, что этот фрагмент немного опасен, он будет работать корректно только тогда, когда вы вызываете его из потока пользовательского интерфейса. SynchronizationContext.Current имеет разные значения в разных потоках. Полезно использовать только поток пользовательского интерфейса. И причина в том, что код должен был скопировать его. Более читаемый и безопасный способ сделать это в приложении Winforms:

    ThreadPool.QueueUserWorkItem(delegate {
        string text = File.ReadAllText(@"c:\temp\log.txt");
        myTextBox.BeginInvoke(new Action(() => {
            myTextBox.Text = text;
        }));
    });

У кого есть то преимущество, что он работает при вызове из любого потока. Преимущество использования SynchronizationContext.Current заключается в том, что он по-прежнему работает независимо от того, используется ли код в Winforms или WPF, это важно в библиотеке. Это, безусловно, не хороший пример такого кода, вы всегда знаете, какой именно TextBox у вас здесь, поэтому вы всегда знаете, использовать ли Control.BeginInvoke или Dispatcher.BeginInvoke. Фактически использование SynchronizationContext.Current не так распространено.

Книга пытается научить вас про потоку, поэтому использование этого ошибочного примера вполне приемлемо. В реальной жизни, в тех немногих случаях, когда вы можете использовать SynchronizationContext.Current, вы все равно оставите это до ключевых слов async/wait С# или TaskScheduler.FromCurrentSynchronizationContext(), чтобы сделать это за вас. Но имейте в виду, что они по-прежнему ошибочно относятся к тому, как это делается, когда вы используете их в неправильном потоке по той же причине. Очень распространенный вопрос здесь, дополнительный уровень абстракции полезен, но затрудняет выяснение, почему они работают неправильно. Надеюсь, книга также расскажет вам, когда не использовать ее:)

Ответ 4

Цель контекста синхронизации заключается в том, чтобы убедиться, что myTextbox.Text = text; вызывается в основном потоке пользовательского интерфейса.

Windows требует, чтобы доступ к элементам управления графическим интерфейсом осуществлялся только с помощью потока, с которым они были созданы. Если вы попытаетесь присвоить текст в фоновом потоке без предварительной синхронизации (с помощью любого из нескольких способов, например, этого или шаблона Invoke), будет выведено исключение.

Что это такое - сохранить контекст синхронизации до создания фонового потока, тогда фоновый поток использует метод context.Post для выполнения кода GUI.

Да, код, который вы показали, в основном бесполезен. Зачем создавать фоновый поток, только для немедленного возврата к основному потоку пользовательского интерфейса? Это просто пример.

Ответ 5

Вы и ваша жена отправляют отдельные подарки отдельным людям. Вы отправляете за своим отцом, и она посылает ее маме. Вы готовите свои пакеты и кладете их на крыльцо. (Thread 0) Вы хотите, чтобы он доставлялся через Fedex (Thread 1), и она хочет через UPS (Thread 2). Вы оба ожидаете уведомления о доставке от одного и того же человека к вашему дому. (контекст синхронизации). Он хватает пакеты и отправляет их через Fedex и UPS. В конце концов, этот человек не должен доставлять уведомления на ваш служебный адрес, потому что он не может войти в здание (нарушение прав доступа, он должен вернуться туда, откуда он вызван). Поэтому, как только ваши пакеты будут доставлены, он возвращается на ваш домашний адрес и оставляет уведомление о доставке пакетов.

Совместное использование ресурсов между Thread 1 и Thread 2 является более сложной аналогией. Вышеупомянутое scenerio - самое основное использование в сетевых вызовах.

Ответ 6

К источнику

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

A SynchronizationContext позволяет вам ставить задачу в другой контекст. Обратите внимание, что каждый поток может иметь свой собственный SynchronizatonContext.

Например: предположим, что у вас есть два потока: Thread1 и Thread2. Скажем, Thread1 выполняет некоторую работу, а затем Thread1 хочет выполнить код на Thread2. Один из возможных способов сделать это - задать Thread2 для объекта SynchronizationContext, передать его Thread1, а затем Thread1 может вызвать SynchronizationContext.Send для выполнения кода в Thread2.