Ранее мы рассматривали EventWaitHandle – простой сигнальный механизм блокировки потока до получения уведомления от другого потока.
Более мощная сигнальная конструкция предоставляется классом Monitor при помощи двух статических методов – Wait и Pulse. Принцип состоит в том, что вы пишете сигнальную логику сами, используя флаги и поля (вместе с оператором lock), а затем вводите команды Wait и Pulse для уменьшения нагрузки на CPU. Преимущество такого низкоуровневого подхода в том, что используя только Wait, Pulse и lock можно получить функциональность AutoResetEvent, ManualResetEvent и Semaphore, а также статических методов WaitHandle – WaitAll и WaitAny. Кроме того, Wait и Pulse можно применить в ситуациях, где любой WaitHandle бросает вызов бережливости.
Проблема Wait и Pulse – скудная документация, особенно в отношении необходимости их применения. И что еще хуже, Wait и Pulse испытывают особенное отвращение к дилетантам: если вы вызываете их без полного понимания, они это узнают – и будут счастливы найти и замучать вас до смерти! К счастью, есть простой образец, которому можно следовать, и который обеспечивает надежное решение в каждом случае.
|
|
Определение Wait и Pulse
Назначение Wait и Pulse – обеспечить простой сигнальный механизм: Wait блокирует, пока не получено уведомление от другого потока, Pulse реализует это уведомление.
Чтобы сигнализация сработала, Wait должен выполняться перед Pulse. Если Pulse выполнится первым, его сигнал будет потерян, и вызванный после него Wait должен будет ожидать следующего сигнала или остаться навсегда заблокированным. Это поведение отличается от AutoResetEvent, у которого метод Set имеет эффект “защелки” и работает, даже если вызван до WaitOne.
Для вызова Wait или Pulse необходимо определить объект синхронизации. Если два потока используют один и тот же объект, они способны посигналить друг другу. Объект синхронизации должен быть заблокирован перед вызовом Wait или Pulse.
Например, если x объявлен следующим образом:
class Test { // Любой объект ссылочного типа может быть объектом синхронизации object x = new object(); } |
то следующий код заблокирует поток на вызове Monitor.Wait:
lock (x) Monitor.Wait(x); |
А этот код (если он выполнен позже в другом потоке) освободит блокированный поток:
lock (x) Monitor.Pulse(x); |