Глава.28 .Асинхронные.операции.ввода-вывода
очередь нужного драйвера устройства (4). Каждый драйвер устройства управляет
собственной очередью запросов ввода-вывода от всех запущенных на машине про-
цессов. При появлении IRP-пакетов драйвер устройства передает содержащуюся в
них информацию соответствующему устройству, которое, собственно, и выполняет
операцию ввода-вывода (5).
рис. 28.1.
.Синхронные.операции.ввода-вывода.в.Windows
Необходимо помнить одну важную подробность: в процессе выполнения
устройством операции ввода-вывода поток исполнения, передавший запрос, про-
стаивает, поэтому Windows переводит его в спящее состояние, чтобы не расходовать
процессорное время впустую (6). Однако при этом поток продолжает занимать
место в памяти своим стеком пользовательского режима, стеком режима ядра,
блоком переменных окружения потока (Thread Environment Block, TEB) и дру-
гими структурами данных, которые в этот момент не используются. Приложения
с графическим интерфейсом перестают реагировать на действия пользователя на
время блокировки потока. Все это, конечно, нежелательно.
После завершения устройством операции ввода-вывода Windows пробуждает
поток, ставит его в очередь процессора и позволяет ему вернуться из режима ядра
сначала в пользовательский режим, а затем и в управляемый код (7, 8 и 9). Метод
Read
объекта
FileStream
при этом возвращает значение типа
Int32
, содержащее
количество прочитанных из файла байтов. Это дает вам информацию о количестве
байтов, оказавшихся в массиве
Byte[]
, ранее переданном методу
Read
.
789
Операции.ввода-вывода.в.Windows
Представим реализацию веб-приложения, в которой для каждого пришедшего
на ваш сервер клиентского запроса формируется запрос к базе данных. При посту-
плении клиентского запроса поток из пула потоков обращается к вашему коду. При
выдаче синхронного запроса к базе данных этот поток окажется заблокированным на
неопределенное время, необходимое для получения ответа из базы. Если в это время
придет еще один клиентский запрос, пул создаст еще один поток, который снова ока-
жется заблокированным. В итоге можно оказаться с целым набором блокированных
потоков, ожидающих ответа из базы данных. Получается, что веб-сервер выделяет
массу ресурсов (потоков и памяти для них), которые почти не используются!
Проблема усугубляется тем, что при получении результатов запросов из базы дан-
ных блокировка с потоков будет снята одновременно и все они начнут исполняться.
В ситуации, когда количество потоков значительно превосходит количество ядер
процессора, операционная система прибегнет к частым переключениям контекста,
что значительно снизит производительность. Так что это не тот путь, который по-
зволил бы реализовать масштабируемое приложение.
Теперь рассмотрим процедуру выполнения асинхронных операций ввода-вывода
в Windows (рис. 28.2). Я убрал со схемы все внешние устройства, кроме жесткого
диска, а также добавил пул потоков среды CLR и слегка отредактировал код. Откры-
тие файла по-прежнему выполняется путем создания объекта
FileStream
, но теперь
ему передается флаг
FileOptions.Asynchronous
, который указывает Windows, что
операции чтения из файла и записи в файл следует выполнять асинхронно.
Чтение данных из файла теперь выполняется методом
BeginRead
, а не методом
Read
.
ReadAsync
создает объект
Task
, представляющий незавершенную
операцию чтения, а затем вызывает Win32-функцию
ReadFile
(1), которая выделяет
место под IRP-пакет, инициализирует его, как и в предыдущем сценарии (2), и пере-
дает в ядро Windows (3). Windows добавляет IRP-пакет в IRP-очередь драйвера
жесткого диска (4), но на этот раз поток не блокируется, а немедленно возвращает
управление после вызовов метода
BeginRead
(5, 6 и 7). Конечно, это может произой-
ти еще до обработки IRP-пакета, поэтому после
ReadAsync
не может следовать код,
который пытается обратиться к байтам в переданном методу массиве
Byte[]
.
Может возникнуть вопрос, когда и каким образом обрабатываются считываемые
данные? При вызове
ReadAsync
возвращается объект
Task
. Используя этот
объект, можно вызвать метод
ContinueWith
для регистрации метода обратного вызо-
ва, который должен выполняться при завершении задачи, а затем обработать данные
в методе обратного вызова. Также можно использовать асинхронные функции C#,
позволяющие использовать последовательную структуру кода (как при выполнении
синхронного ввода-вывода).
Закончив обработку IRP-пакета (
a
), устройство помещает делегат в очередь
CLR-пула потоков (
b
). В дальнейшем какой-то из потоков пула берет готовый IRP-
пакет и активизирует метод обратного вызова (
c
)
1
. В результате вы узнаете о за-
1
Готовые IRP-пакеты извлекаются из пула по алгоритму «первым пришел — первым
обслужен».
790
Do'stlaringiz bilan baham: |