События в С#

В .NET Framework вы можете использовать три типа для работы с сигнальными событиями: ManualResetEvent, AutoResetEvent и EventWaitHandle. Подобно Mutex, эти объекты событий отображаются непосредственно на объекты событий Win32.

Если вы знакомы с применением событий Win32, то с объектами событий .NET почувствуете себя как дома. Как и в случае с Mutex, работа с объектами событий влечет за собой медленные переключения в режим ядра.

Оба типа событий становятся сигнальными, когда кто-то вызывает метод Set на экземпляре события. В этой точке поток, ожидающий события, освобождается. Потоки ожидают события, вызывая унаследованный метод WaitOne — тот же самый, который вызывается для ожидания, когда Mutex станет сигнальным.

Утверждение, что ожидающий поток освобождается, когда событие становится сигнальным, следует выдвигать осторожно. Возможно такое, что множество потоков могут быть освобождено, когда событие становится сигнальным. В этом фактически состоит разница между ManualResetEvent и AutoResetEvent.

Когда ManualResetEvent становится сигнальным, все ожидающие на нем потоки освобождаются. Оно остается сигнальным до тех пор, пока кто-то не обратится к его методу Reset.

Если любой поток вызывает WaitOne, когда ManualResetEvent уже является сигнальным, то ожидание немедленно завершается успешно. С другой стороны, объекты AutoResetEvent освобождают только один ожидающий поток и затем немедленно автоматически переходят в несигнальное состояние.

Можно представить, что все потоки, ожидающие AutoResetEvent, находятся в очереди, в которой только первый поток из очереди освобождается, когда событие становится сигнальным.

Однако, несмотря на то, что удобно предположить, что ожидающие потоки стоят в очереди, нельзя выдвигать какие-либо предположения о том, какой именно ожидающий поток будет освобожден первым. По причине такого поведения тип AutoResetEvent также называют событием синхронизации.

С использованием типа AutoResetEvent можно реализовать черновой пул потоков, в котором несколько потоков ожидают сигнала от AutoResetEvent, сообщающего, что определенная часть работы доступна. Когда в очередь добавляется новая часть работы, событие сигнализируется, чтобы прекратить ожидание одного из потоков. Такая реализация пула потоков неэффективна и не лишена проблем.

Например, ситуация усложняется, когда все потоки заняты, а элементы работы продолжают поступать в очередь, особенно если только одному потоку разрешено завершать одну единицу работы, прежде чем возвращаться в очередь ожидания.

Если все потоки заняты, а тем временем, скажем, пять единиц работы находятся в очереди, событие будет сигнализировано, но ни один поток не будет пребывать в состоянии ожидания. Первый поток в очереди ожидания будет освобожден, когда вызовет WaitOne, но другие — нет, даже несмотря на то, что в очереди находятся еще четыре элемента работы.

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

Вообще-то, это не выход, поскольку при этом некоторая логика синхронизации возлагается на поток, пытающийся поместить работу в очередь, заставляя его как-то реагировать на неудачные попытки поместить единицу работы в очередь. На самом деле создание эффективного пула потоков — задача, по меньшей мере, непростая. Поэтому перед тем, как решиться приступить к ней, рекомендуется использовать класс ThreadPool.

Поскольку объекты событий .NET основаны на объектах событий Win32, их можно применять для синхронизации выполнения между несколькими процессами. Наряду с Mutex, они также менее эффективны, чем альтернатива, подобная классу Monitor, поскольку также требуют переключения в режим ядра.

Однако создатели ManualResetEvent и AutoResetEvent не предусмотрели возможности именования объектов событий в конструкторах, как это сделано для объекта Mutex. Поэтому если требуется создать именованное событие, вместо этого следует использовать класс EventWaitHandle, представленный в .NET 2.0.

В библиотеке .NET 4.0 BCL появился новый тип по имени ManualResetEventSlim, который представляет собой легковесную, свободную от блокировок реализацию события ручного сброса. Однако он может применяться только в межпотоковых коммуникациях внутри одного процесса.

Если необходимо синхронизировать несколько потоков, вместо него следует использовать ManualResetEvent или AutoResetEvent.

Блокирующие объекты

Платформа .NET Framework предоставляет несколько высокоуровневых блокирующих объектов, которые можно использовать для синхронизации доступа к данным из множества потоков.

Однако класс Monitor не реализует объект блокировки ядра; вместо этого он предоставляет доступ к блокировке синхронизации каждого экземпляра объекта .NET. Ранее также рассматривались методы примитивного класса Interlocked, которые можно применять для реализации спин-блокировок.

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

Другие высокоуровневые блокирующие объекты обычно разрешают это, до тех пор, пока обеспечивается соответствие между количеством операций установки блокировки и количеством операций по ее освобождению. В настоящем разделе речь пойдет о некоторых полезных блокирующих объектах, предлагаемых .NET Framework.

Независимо от используемого типа блокирующих объектов, всегда нужно стремиться писать код, который обеспечивает как можно более кратковременное удержание блокировки.

Например, если блокировка захвачена для доступа к некоторым данным внутри метода, который может потребовать ощутимого времени для обработки данных, удерживайте блокировку лишь столько времени, сколько необходимо для создания копии в локальном стеке, и затем как можно скорее снимайте блокировку.

Применение такой техники гарантирует, что другие потоки в системе не будут заблокированы в течение слишком длительного времени, ожидая доступа к тем же самым данным.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100