В книге «Программирование на C #» есть пример кода о 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
метод запускается, что будет делать поток пользовательского интерфейса?
Если я попрошу глупостей, пожалуйста, поправьте меня, спасибо!
РЕДАКТИРОВАТЬ: Например, что, если я просто напишу myTextBox.Text = text;
в методе, в чем разница?
c#
.net
multithreading
cloudyFan
источник
источник
async
/await
полагаетсяSynchronizationContext
внизу.Ответы:
Проще говоря,
SynchronizationContext
представляет собой место, «где» код может быть выполнен. Затем в этом месте будут вызываться делегаты, переданные его методуSend
или . ( это неблокирующая / асинхронная версияPost
Post
Send
.)Каждый поток может иметь
SynchronizationContext
связанный с ним экземпляр. Выполняющийся поток можно связать с контекстом синхронизации путем вызова статическогоSynchronizationContext.SetSynchronizationContext
метода , а текущий контекст работающего потока можно запросить черезSynchronizationContext.Current
свойство .Несмотря на то, что я только что написал (каждый поток имеет связанный контекст синхронизации), a
SynchronizationContext
не обязательно представляет конкретный поток ; он также может перенаправлять вызов переданных ему делегатов в любой из нескольких потоков (например, вThreadPool
рабочий поток) или (по крайней мере, теоретически) на конкретное ядро ЦП или даже на другой сетевой хост . Где будут работать ваши делегаты, зависит от типаSynchronizationContext
используемого.Windows Forms установит
WindowsFormsSynchronizationContext
в поток, в котором создается первая форма. (Этот поток обычно называют «потоком пользовательского интерфейса».) Этот тип контекста синхронизации вызывает делегатов, переданных ему именно в этом потоке. Это очень полезно, поскольку Windows Forms, как и многие другие фреймворки пользовательского интерфейса, разрешает манипулирование элементами управления только в том же потоке, в котором они были созданы.Код, который вы передали,
ThreadPool.QueueUserWorkItem
будет выполняться в рабочем потоке пула потоков. То есть он не будет выполняться в потоке, в котором вашmyTextBox
был создан, поэтому Windows Forms рано или поздно (особенно в сборках Release) выдаст исключение, сообщающее вам, что вы не можете получить доступmyTextBox
из другого потока.Вот почему вам нужно каким-то образом «переключиться обратно» с рабочего потока на «поток пользовательского интерфейса» (где
myTextBox
был создан) перед этим конкретным назначением. Это делается следующим образом:Пока вы все еще находитесь в потоке пользовательского интерфейса,
SynchronizationContext
сохраните там Windows Forms и сохраните ссылку на них в переменной (originalContext
) для дальнейшего использования. Вы должны сделать запросSynchronizationContext.Current
на этом этапе; если вы запросили его в переданном кодеThreadPool.QueueUserWorkItem
, вы можете получить любой контекст синхронизации, связанный с рабочим потоком пула потоков. После того, как вы сохранили ссылку на контекст Windows Forms, вы можете использовать ее в любом месте и в любое время для «отправки» кода в поток пользовательского интерфейса.Всякий раз, когда вам нужно манипулировать элементом пользовательского интерфейса (но его нет или может не быть в потоке пользовательского интерфейса), обращайтесь к контексту синхронизации Windows Forms через
originalContext
и передайте код, который будет управлять пользовательским интерфейсом, либоSend
илиPost
.Заключительные замечания и подсказки:
Что контексты синхронизации не будет делать для вас говорит вам , какой код должен работать в определенном месте / контекста, и какой код может быть просто выполнен нормально, не пропуская ее к
SynchronizationContext
. Чтобы решить это, вы должны знать правила и требования фреймворка, для которого вы программируете - в данном случае Windows Forms.Так что запомните это простое правило для Windows Forms: НЕ обращайтесь к элементам управления или формам из потока, отличного от того, который их создал. Если вы должны это сделать, используйте
SynchronizationContext
описанный выше механизм илиControl.BeginInvoke
(который является специфичным для Windows Forms способом сделать то же самое).Если вы программируете на .NET 4.5 или более поздней версии, вы можете сделать вашу жизнь намного проще путем преобразования кода , который явно использует
SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
и т.д. к новымasync
/await
ключевых слов и Task Parallel Library (TPL) , то есть API окружающейTask
иTask<TResult>
классы. Они в очень высокой степени позаботятся о захвате контекста синхронизации потока пользовательского интерфейса, запуске асинхронной операции, а затем возвращении в поток пользовательского интерфейса, чтобы вы могли обработать результат операции.источник
Application.Run
IIRC). Это довольно сложная тема, и она не происходит случайно.null
), либо не является его экземпляромSynchronizationContext
(или его подклассом). Смысл этой цитаты был не в том, что вы получите, а в том, чего вы не получите: контекст синхронизации потока пользовательского интерфейса.Я хотел бы добавить к другим ответам,
SynchronizationContext.Post
просто ставит в очередь обратный вызов для последующего выполнения в целевом потоке (обычно во время следующего цикла цикла сообщений целевого потока), а затем выполнение продолжается в вызывающем потоке. С другой стороны,SynchronizationContext.Send
пытается немедленно выполнить обратный вызов в целевом потоке, что блокирует вызывающий поток и может привести к тупиковой ситуации. В обоих случаях существует возможность повторного входа в код (ввод метода класса в том же потоке выполнения до возврата из предыдущего вызова того же метода).Если вы знакомы с моделью программирования Win32, очень близкая аналогия была бы
PostMessage
иSendMessage
API, которые вы можете вызвать к отправке сообщений из другого потока от одного целевого окна.Вот очень хорошее объяснение того, что такое контексты синхронизации: Все о контексте синхронизации .
источник
В нем хранится поставщик синхронизации, класс, производный от SynchronizationContext. В этом случае это, вероятно, будет экземпляр WindowsFormsSynchronizationContext. Этот класс использует методы Control.Invoke () и Control.BeginInvoke () для реализации методов Send () и Post (). Или это может быть DispatcherSynchronizationContext, он использует Dispatcher.Invoke () и BeginInvoke (). В приложении Winforms или WPF этот провайдер устанавливается автоматически, как только вы создаете окно.
Когда вы запускаете код в другом потоке, например поток пула потоков, используемый во фрагменте, вы должны быть осторожны, чтобы напрямую не использовать объекты, небезопасные для потоков. Как и любой объект пользовательского интерфейса, вы должны обновить свойство TextBox.Text из потока, создавшего TextBox. Метод Post () гарантирует, что целевой делегат работает в этом потоке.
Помните, что этот фрагмент немного опасен, он будет работать правильно только тогда, когда вы вызовете его из потока пользовательского интерфейса. SynchronizationContext.Current имеет разные значения в разных потоках. Только поток пользовательского интерфейса имеет полезное значение. И это причина, по которой код должен был его скопировать. Более читаемый и безопасный способ сделать это в приложении Winforms:
Это имеет то преимущество, что он работает при вызове из любого потока. Преимущество использования SynchronizationContext.Current заключается в том, что он по-прежнему работает независимо от того, используется ли код в Winforms или WPF, это имеет значение в библиотеке. Это, конечно, не лучший пример такого кода, вы всегда знаете, какой у вас TextBox, поэтому вы всегда знаете, использовать ли Control.BeginInvoke или Dispatcher.BeginInvoke. На самом деле использование SynchronizationContext.Current не так уж часто.
Книга пытается научить вас многопоточности, поэтому использовать этот ошибочный пример - нормально. В реальной жизни в тех немногих случаях, когда вы могли бы рассмотреть возможность использования SynchronizationContext.Current, вы все равно оставите это на усмотрение ключевых слов async / await C # или TaskScheduler.FromCurrentSynchronizationContext (), чтобы сделать это за вас. Но обратите внимание, что они по-прежнему ведут себя плохо, как фрагмент, когда вы используете их не в том потоке, по той же причине. Очень распространенный вопрос здесь: дополнительный уровень абстракции полезен, но затрудняет понимание, почему они не работают правильно. Надеюсь, книга также подскажет, когда ее не использовать :)
источник
Цель контекста синхронизации здесь - убедиться, что он вызывается
myTextbox.Text = text;
в основном потоке пользовательского интерфейса.Windows требует, чтобы к элементам управления GUI имел доступ только поток, в котором они были созданы. Если вы попытаетесь назначить текст в фоновом потоке без предварительной синхронизации (с помощью любого из нескольких способов, таких как этот или шаблон Invoke), будет создано исключение.
При этом сохраняется контекст синхронизации перед созданием фонового потока, затем фоновый поток использует метод context.Post для выполнения кода графического интерфейса.
Да, код, который вы показали, в основном бесполезен. Зачем создавать фоновый поток только для того, чтобы немедленно вернуться к основному потоку пользовательского интерфейса? Это просто пример.
источник
К источнику
Например: Предположим, у вас есть два потока, Thread1 и Thread2. Скажем, Thread1 выполняет некоторую работу, а затем Thread1 хочет выполнить код в Thread2. Один из возможных способов сделать это - запросить у Thread2 его объект SynchronizationContext, передать его Thread1, а затем Thread1 может вызвать SynchronizationContext.Send для выполнения кода в Thread2.
источник
SynchronizationContext предоставляет нам способ обновить пользовательский интерфейс из другого потока (синхронно с помощью метода Send или асинхронно с помощью метода Post).
Взгляните на следующий пример:
SynchronizationContext.Current вернет контекст синхронизации потока пользовательского интерфейса. Откуда я это знаю? В начале каждой формы или приложения WPF контекст будет установлен в потоке пользовательского интерфейса. Если вы создадите приложение WPF и запустите мой пример, вы увидите, что когда вы нажмете кнопку, оно спит примерно 1 секунду, а затем отобразит содержимое файла. Вы можете ожидать, что этого не произойдет, потому что вызывающий метод UpdateTextBox (которым является Work1) - это метод, переданный потоку, поэтому он должен засыпать этот поток, а не основной поток пользовательского интерфейса, NOPE! Хотя метод Work1 передается в поток, обратите внимание, что он также принимает объект, который является SyncContext. Если вы посмотрите на него, вы увидите, что метод UpdateTextBox выполняется через метод syncContext.Post, а не через метод Work1. Взгляните на следующее:
Последний пример и этот выполняются одинаково. Оба не блокируют пользовательский интерфейс, пока он выполняет свою работу.
В заключение представьте, что SynchronizationContext - это поток. Это не поток, он определяет поток (обратите внимание, что не все потоки имеют SyncContext). Всякий раз, когда мы вызываем для него метод Post или Send для обновления пользовательского интерфейса, это похоже на обновление пользовательского интерфейса обычно из основного потока пользовательского интерфейса. Если по каким-либо причинам вам необходимо обновить пользовательский интерфейс из другого потока, убедитесь, что этот поток имеет SyncContext основного потока пользовательского интерфейса, и просто вызовите для него метод Send или Post с методом, который вы хотите выполнить, и все устанавливать.
Надеюсь, это поможет тебе, дружище!
источник
SynchronizationContext в основном является поставщиком выполнения делегатов обратного вызова, в основном отвечающим за то, чтобы делегаты запускались в заданном контексте выполнения после того, как определенная часть кода (инкапсулированная в Task obj .Net TPL) программы завершила свое выполнение.
С технической точки зрения SC - это простой класс C #, ориентированный на поддержку и предоставление своих функций специально для объектов параллельной библиотеки задач.
Каждое приложение .Net, за исключением консольных приложений, имеет конкретную реализацию этого класса, основанную на конкретной базовой структуре, например: WPF, WindowsForm, Asp Net, Silverlight и т. Д.
Важность этого объекта связана с синхронизацией между результатами, возвращаемыми из асинхронного выполнения кода, и выполнением зависимого кода, который ожидает результатов этой асинхронной работы.
А слово «контекст» означает контекст выполнения, то есть текущий контекст выполнения, в котором будет выполняться этот ожидающий код, а именно синхронизация между асинхронным кодом и его кодом ожидания происходит в определенном контексте выполнения, поэтому этот объект называется SynchronizationContext: он представляет собой контекст выполнения, который будет следить за синхронизацией асинхронного кода и ожидающим выполнением кода .
источник
Этот пример взят из примеров Linqpad от Джозефа Альбахари, но он действительно помогает понять, что делает контекст синхронизации.
источник