Когда вы программируете GUI приложения, вы хорошо знаете, что прокачка сообщений (message pumping) является основным способом по приёму и диспетчеризации сообщений. Невизуальным аналогом прокачки сообщений является ожидание в тревожном состоянии (alertable wait).
APC пользовательского режима (Asynchronous Procedure Call - асинхронный вызов процедуры) - это запрос запуска функции в потоке пользовательского режима. Вы можете явно поставить APC в очередь потока с помощью функции
QueueUserAPC, либо же вы можете неявно передать функцию как функцию завершения (обратного вызова) для ожидаемого таймера или асинхронного ввода-вывода (вот почему флаг, который указывает, что ожидание прервано выполнением APC, называется WAIT_IO_COMPLETION: изначально единственной вещью в очередях APC могло быть только функция завершения асинхронного ввода-вывода).Конечно же, когда вы планируете вызов APC, то функция не может быть вызвана немедленно. Представьте, на что был бы похож мир, если бы это было возможно: функция прервала бы поток на половине того, чем бы он там ни занимался - возможно, структуры данных программы при этом были в нестабильном рабочем состоянии, что означает, что APC функции придётся работать в условиях несогласованного состояния программы. Если бы APC действительно работали бы так (прим.пер.: это, кстати, несложно сделать самому вручную), то было бы невозможно написать осмысленную APC функцию, потому что она не может надёжно обращаться к любым переменным (потому что переменные могут находиться в нестабильном состоянии), не может она и вызывать любые функции, которые обращаются к этим переменным. Учитывая эти ограничения, функция может делать не так уж много вещей.
Поэтому вместо этого APC функции диспетчеризируются когда вы выполняете так называемое "ожидание в тревожном состоянии" (alertable wait). "Ex"-варианты большинства функций ожидания (к примеру,
WaitForSingleObjectEx, WaitForMultipleObjectsEx, MsgWaitForMultipleObjectsEx и SleepEx) позволяют вам указать, не хотите ли вы ждать в тревожном состоянии. Если вы включаете этот режим и в APC-очереди потока появляются одна или несколько APC-функций, то операция ожидания завершается раньше времени, возвращая код, указывающий, что ожидание было прервано выполнением APC. Если APC, выполнения которых вы ждали, ещё не выполнены (скажем, вы выполнили чей-то сторонний APC-запрос), то перезапустить ожидание будет вашей ответственностью.Почему операционная система сама не перезапускает ожидание автоматически? "Представьте себе мир, в котором это было бы так": предположим, вы запускаете асинхронный ввод-вывод, а параллельно делаете ещё что-то, а затем ждёте завершения ввода-вывода, чтобы использовать результат:
// Когда завершится асинхронное чтение, мы запустим
// следующий запрос на чтение. Когда закончим - установим fCompleted.
var
fCompleted: Boolean = False;
fSuccess: Boolean;
procedure CompletionRoutine(dwErrorCode, dwNumberOfBytesTransfered: DWORD; Overlapped: LPOverlapped); stdcall;
begin
if { закончили } then
begin
fSuccess := True;
fCompleted := True;
end
else
begin
// Запускаем следующий запрос в последовательности
if not ReadFileEx(hFile, ..., @CompletionRoutine) then
begin
fSuccess := False; // возникла проблема
fCompleted := True; // не можем продолжать при проблеме
end;
end;
end;
...
// Запускаем чтение и выполняем другую работу
if ReadFileEx(hFile, ..., @CompletionRoutine) then
DoOtherStuffInTheMeantime;
{ ждём установки fCompleted }
DoStuffWithResult;
Как бы вы написали действие "ждём установки fCompleted", если бы операционная система перезапускала ожидание? Если вы запустите бесконечное ожидание в тревожном состоянии (скажем, через SleepEx(INFINITE, True)), то APC отработают, ОС перезапустит ожидание и sleep будет выполняться вечно. Вам пришлось бы использовать конечный (небольшой) интервал ожидания и постоянно проверять результаты ожидания (poll). Но у этого подхода есть два серьёзных недостатка: во-первых, опрос - это очень плохо. Во-вторых, скорость, с которой вы производите опрос, напрямую влияет на скорость, с которой ваша программа реагирует на завершение чтений в цепочке. Более высокая частота опроса даёт вам лучшую отзывчивость, но также и тратит больше CPU времени впустую.К счастью, ожидание не перезапускается автоматически. Это даёт вам шанс самому решить, хотите ли вы делать перезапуск или нет:
...
// Запускаем чтение и выполняем другую работу
if ReadFileEx(hFile, ..., @CompletionRoutine) then
DoOtherStuffInTheMeantime;
while not fCompleted do
SleepEx(INFINITE, True);
DoStuffWithResult;
Цикл со SleepEx ждёт бесконечно, параллельно обрабатывая APC, пока функция обратного вызова не решит, что работа выполнена и установит флаг fCompleted.
Если бы APC действительно работали бы так (прим.пер.: это, кстати, несложно сделать самому вручную)
ОтветитьУдалитьПомоему писать драйвер довольно сложно.
Имелось в виду вызывать функцию в потоке в любой момент времени. Для этого не нужен драйвер, достаточно функций смены контекста потока.
ОтветитьУдалить