Асинхронный файловый ввод/вывод в операционной системе WINDOWS, страница 9

Звучит неплохо, однако есть небольшая особенность, существенно снижающая привлекательность этих вызовов. Дело в том, что для вызова функции завершения используется АРС. Это означает, что для того, чтобы произошло обращение к функции завершения, поток, обратившийся к ReadFileEx или WriteFileEx, должен находиться в «настороженном» состоянии. Чтобы перейти в настороженное состояние, необходимо использовать вызовы WaitForSingleObject (или любой другой из этой категории) или SleepEx. Находясь в настороженном состоянии, поток не выполняет никаких действий, а просто ждет, когда в результате завершения операции ввода/вывода произойдет обращение к функции завершения.

Все эти особенности перекрывающегося ввода/вывода несколько обескураживают. Перекрывающийся ввод/вывод удобен во многих ситуациях, однако он не настолько асинхронный, как этого хотелось бы программистам. Чтобы реализовать действительно асинхронный ввод/вывод, следует создать отдельный программный поток, в рамках которого использовать обычные функции ввода/вывода. В качестве альтернативы при разработке некоторых программ можно использовать порты завершения ввода/вывода (см. ниже).

Определение EOF

Если традиционная функция чтения данных в процессе осуществления операции ввода встречает символ EOF, она тем или иным образом оповещает об этом вызывающую программу. В частности, традиционный вызов ReadFile устанавливает количество прочитанных байт равным нулю. Если символ EOF встречается в процессе выполнения перекрывающегося вызова ReadFile, вызов возвращает ошибку. В этом случае значение, возвращаемое функцией GetLastError, будет равно ERROR_HANDLE_EOF. Это значение может быть получено не только в результате выполнения ReadFile, но также и при обращении к GetOverlappedResult.

Наверное можно считать, что это соответствует нормальной логике функционирования системы. Символ EOF может встретиться непосредственно при обращении к функции ReadFile или позже, в процессе выполнения операции ввода/вывода. В любом случае следует проверить наличие EOF, используя при этом метод, соответствующий типу ввода/вывода, который вы применяете.

Порты завершения ввода/вывода

Перекрывающийся ввод/вывод обладает массой ограничений. Фактически при использовании перекрывающегося ввода/вывода для обмена данными с некоторым объектом (файлом или устройством) используется отдельный программный поток. Например, если вы планируете использовать перекрывающийся ввод/вывод при разработке сетевого сервера, обслуживанием каждого из клиентов будет заниматься отдельный программный поток. Такая схема будет отлично работать в случае, если число клиентов небольшое. Однако вряд ли удастся использовать подобный подход для обеспечения работы высокопроизводительного сервера, обслуживающего одновременно достаточно большое количество клиентов. Не всякий компьютер сможет обеспечить создание и поддержку работы нескольких тысяч программных потоков одновременно. Может ли один поток обслуживать одновременно нескольких клиентов? Этого можно достичь, если использовать порты завершения ввода/вывода (I/O completion ports).

Порт завершения ввода/вывода напоминает очередь. В эту очередь заносятся уведомления о том, что та или иная процедура ввода/вывода завершена. Любой поток может проверить очередь и отреагировать на любое из этих уведомлений. Прежде чем можно будет приступить к использованию порта завершения ввода/ вывода, необходимо создать его при помощи вызова CreateIoCompletionPort. В качестве одного из аргументов этот вызов принимает дескриптор файла, открытого для перекрывающегося ввода/вывода. Если вы установили соответствие между дескриптором файла и портом завершения ввода вывода, вы больше не сможете использовать для работы с этим дескриптором функции ReadFileEx и WriteFileEx. Кроме того, не рекомендуется передавать этот дескриптор другим процессам при помощи наследования или при помощи вызова DuplicateHandle.