Объекты синхронизации Win32 и WaitHandle

В предъщущих двух разделах рассказывалось об объектах Mutex, ManualResetEvent и AutoResetEvent. Каждый из этих типов наследуется от WaitHandle — общего механизма, который можно использовать в .NET Framework для управления любого рода объектами синхронизации Win32.

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

Речь идет об управлении, а не инкапсуляции, потому что класс WaitHandle не решает, как следует, задачу инкапсуляции, да он для этого и не предназначен. Он предназначен просто служить оболочкой, которая при работе с этими дескрипторами операционной системы помогает избежать массы прямых вызовов Win32 через уровень P/Invoke.

Стоит потратить некоторое время и разобраться, когда и как нужно использовать WaitHanle, потому что многие API-интерфейсы все еще не получили своего отображения в .NET Framework, а многие из них, возможно, никогда и не получат.

Применение метода WaitOne для ожидания, когда объект станет сигнальным, уже обсуждалось. Однако класс WaitHandle имеет два статических метода, которые можно использовать для организации ожидания на нескольких объектах. Первым из них является WaitHandle.WaitAny.

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

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

В случае вызова WaitAll возвращается булевское значение: true в случае, если все объекты стали сигнальными, и false — в случае выхода по таймауту.

До появления класса EventWaitHandle в .NET 2.0, чтобы получить именованное событие, нужно было создать лежащий в основе объект Win32, а затем упаковать его в WaitHandle, как это сделано в следующем примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
public class NamedEventCreator {
[DllImport( "KERNEL32.DLL", EntryPoint="CreateEventW", SetLastError=true ) ] 
private static extern SafeWaitHandle CreateEvent(IntPtr IpEventAttributes, bool bManualReset, bool blnitialState, string lpName ) ; 
public static AutoResetEvent CreateAutoResetEvent (bool initialState, string name )  {
// Создать именованное событие.
SafeWaitHandle rawEvent = CreateEvent ( IntPtr.Zero, false, initialState, name ) ;
if ( rawEvent.Islnvalid )  { 
throw new Win32Exception(Marshal.GetLastWin32Error() ) ;
}
// Создать событие управляемого типа на основе дескриптора. 
AutoResetEvent autoEvent = new AutoResetEvent( false ) ; 
// Очистить дескриптор, находящийся в autoEvent, 
// перед заменой его именованным дескриптором. 
autoEvent.SafeWaitHandle = rawEvent; 
return autoEvent;
}

Здесь при вызове Win32-функция CreateEventW для создания именованного события применялся уровень P/Invoke. В этом примере следует отметить несколько моментов. Например, ставка сделана полностью на безопасность дескрипторов Win32, как это принято и в остальных классах стандартной библиотеки классов .NET Framework.

Поэтому первым параметром CreateEvent является IntPtr.Zero, что является лучшим способом передачи указателя NULL на ошибку Win32 для параметра LPSECURITY_ ATTRIBUTES. Обратите внимание, что успех или неудача создания события определяется путем проверки свойства Is Invalid экземпляра SafeWaitHandle.

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

WaitHandle предоставляет свойство по имени SafeWaitHandle, посредством которого можно модифицировать лежащий в основе дескриптор Win32 в любом типе-наследнике WaitHandle.

Возможно, вы заметили в документации упоминание унаследованного свойства Handle. Этого свойства лучше избегать, поскольку повторное присваивание ему нового дескриптора ядра не закрывает предыдущий дескриптор, в результате чего происходит утечка ресурсов, если только не закрыть дескриптор самостоятельно. Вместо него должны применяться типы-наследники SafeHandle. Тип SafeHandle также использует ограниченные области выполнения, чтобы предотвратить утечки ресурсов в случае асинхронных исключений вроде ThreadAbortException.

В предыдущем примере метод CreateEvent объявлен как возвращающий Saf eWaitHandle. Хотя это не очевидно из документации по SafeHandle, данный класс имеет приватный конструктор по умолчанию, который уровень P/Invoke может использовать для создания и инициализации экземпляра этого класса.

Ознакомьтесь с остальными типами, производными от SafeHandle, которые определены в пространстве имен Microsoft .Win32 . SafeHandles.

В частности, в .NET 2.0 Framework для удобства определения собственных основанных на Win32 наследников SafeWaitHandle предлагаются типы SafeHandleMinusOnelsInvalid и Saf eHandleZeroOrMinusOnelsInvalid. Они очень удобны, поскольку, к сожалению, для представления ошибочных ситуаций в различных подразделах Win32 API используются разные значения возвращаемых дескрипторов.

Имейте в виду, что тип WaitHandle реализует интерфейс IDisposable. В связи с этим всякий раз, когда в коде используются экземпляры WaitHandle или любых его классов-наследников, таких как Mutex, AutoResetEvent и ManualResetEvent, разумно применять ключевое слово using.

Еще один момент, связанный с использованием объектов WaitHandle и его наследников: когда управляемые потоки заблокированы методами WaitHandle, завершить или прервать их не удастся. Поскольку действительный поток операционной системы, который стоит за управляемым потоком, блокируется внутри ОС, т.е. вне управляемой исполняющей среды, он может быть прерван только тогда, когда вернется в управляемую среду.

Поэтому в случае вызова Abort или Interrupt на одном из этих потоков операция будет отложена до тех пор, пока не завершится ожидание потока на уровне операционной системы. Об этом следует помнить при установке блокировки с использованием объекта WaitHandle в управляемых потоках.

Вы можете следить за любыми ответами на эту запись через 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