Когда вы программируете 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 действительно работали бы так (прим.пер.: это, кстати, несложно сделать самому вручную)
ОтветитьУдалитьПомоему писать драйвер довольно сложно.
Имелось в виду вызывать функцию в потоке в любой момент времени. Для этого не нужен драйвер, достаточно функций смены контекста потока.
ОтветитьУдалить