Эмуляция завершения запросов ввода-вывода
октября 12 2009 by admin in Обязательный материалПорт завершения ввода-вывода можно использовать вовсе не для ввода-вывода на устройства. Эта глава посвящена и межпотоковому взаимодействию, а объект ядра, управляющий портом завершения ввода-вывода, может в этом поспособствовать. В разделе «Оповестительный ввод-вывод» я представил функцию QueueUserAPC, которая позволяет поместить запись асинхронного вызова процедуры в очередь другого потока. С портом завершения ввода-вывода используется подобная — PostQueuedCompletionStatus:
BOOL PostQueuedCompletionStatus( HANDLE hCompPort, DWORD dwNumBytes, UL0NG_PTR CompKey, OVERLAPPED* pOverlapped);
Функция добавляет уведомление о завершении ввода-вывода в очередь порта завершения ввода-вывода. Параметр hCompPort определяют порт завершения, в очередь которого вы хотите поместить запись; остальные три (dwNumBytes, СотрКоу и pOverlapped) указывают значения, которые должны быть возвращены потоку функцией GetQueuedCompletionStatus. Когда поток извлекает эмулированную запись из очереди завершения ввода-вывода, GetQueuedCompletionStatus возвращает TRUE, указывая на успешное завершение запроса ввода-вывода.
PostQueuedCompletionStatus дает возможность обмениваться информацией со всеми потоками в пуле. Например, когда пользователь прерывает работу службы, вам надо, чтобы все потоки корректно прекратили работу. Но если есть потоки, которые ожидают в порте завершения ввода-вывода, а запросы ввода-вывода не поступают, эти потоки остаются заблокированными. Если же для каждого потока в пуле один раз обратиться к PostQueuedCompletionStatus, можно разблокировать каждый из потоков, проанализировать возвращенные из GetQueuedCompletionStatus значения, увидеть, что приложение прерывает работу, освободить ресурсы и закончить выполнение потока.
Подобную технику прерывания работы потока следует применять с осторожностью. Мой пример работает, потому что потоки из пула прекращают работу и больше не обращаются к GetQueuedCompletionStatus. Но если вы хотите о чем-то уведомлять каждый поток в пуле, чтобы затем они снова в цикле переходили к вызову GetQueuedCompletionStatus, ждите проблем: ведь потоки разблокируются в порядке, обратном порядку их постановки в очередь. Так что в вашем приложении потребуется дополнительная синхронизация потоков, которая смогла бы гарантировать, что каждый поток из пула сможет получить запись, эмулирующую ввод-вывод. Иначе один поток может получить одно уведомление несколько раз.