Что делает 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.