Выполнение процедуры завершения и возврат из функции дежурного ожидания

По окончании выполнения операции расширенного ввода/вывода связанная с ней процедура завершения со своими аргументами, определяющими структуру OVERLAPPED, счетчик байтов и код ошибки, помещается в очередь для выполнения.

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

Если возврат из функции SleepEx обусловлен выполнением одной или нескольких процедур завершения, находящихся в очереди, то возвращаемым значением функции будет WAIT_TO_COMPLETION, и это же значение будет возвращено функцией GetLastError, вызванной после выполнения возврата одной из функций ожидания.

В заключение отметим два момента:

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

2. Для передачи информации процедуре завершения общепринято использовать элемент данных hEvent структуры OVERLAPPED, поскольку это поле игнорируется ОС.

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

Рис. 14.2.Асинхронный ввод/вывод с использованием процедур завершения

Пример: преобразование файла с использованием расширенного ввода/вывода

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

Программа 14.2. atouEx: преобразование файла с использованием расширенного ввода/вывода

/* Глава 14. atouEX

Преобразование файла из ASCII в Unicode средствами РАСШИРЕННОГО ВВОДА/ВЫВОДА. */

/* atouEX файл1 файл2 */

 

#include "EvryThng.h"

#define MAX_OVRLP 4

#define REC_SIZE 8096 /* Размер блока не имеет столь важного значения в отношении производительности, как в случае atouOV. */

#define UREC_SIZE 2 * REC_SIZE

 

static VOID WINAPI ReadDone(DWORD, DWORD, LPOVERLAPPED);

static VOID WINAPI WriteDone(DWORD, DWORD, LPOVERLAPPED);

 

/* Первая структура OVERLAPPED предназначена для чтения, а вторая — для записи. Структуры и буферы распределяются для каждой предстоящей операции. */

OVERLAPPED OverLapIn[MAX_OVRLP], OverLapOut [MAX_OVRLP];

CHAR AsRec[MAX_OVRLP][REC_SIZE];

WCHAR UnRec[MAX_OVRLP][REC_SIZE];

HANDLE hInputFile, hOutputFile;

LONGLONG nRecord, nDone;

LARGE_INTEGER FileSize;

 

int _tmain(int argc, LPTSTR argv[]) {

DWORD ic;

LARGE_INTEGER CurPosIn;

hInputFile = CreateFile(argv[1], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

hOutputFile = CreateFile(argv[2], GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);

FileSize.LowPart = GetFileSize(hInputFile, &FileSize.HighPart);

nRecord = FileSize.QuadPart / REC_SIZE;

if ((FileSize.QuadPart % REC_SIZE) != 0) nRecord++;

CurPosIn.QuadPart = 0;

for (ic = 0; ic < MAX_OVRLP; ic++) {

OverLapIn[ic].hEvent = (HANDLE)ic; /* Перегрузить событие. */

OverLapOut[ic].hEvent = (HANDLE)ic; /* Поля. */

OverLapIn[ic].Offset = CurPosIn.LowPart;

OverLapIn[ic].OffsetHigh = CurPosIn.HighPart;

if (CurPosIn.QuadPart < FileSize.QuadPart) ReadFileEx(hInputFile, AsRec[ic], REC_SIZE, &OverLapIn [ic], ReadDone);

CurPosIn.QuadPart += (LONGLONG)REC_SIZE;

}

/* Выполняются все операции чтения. Войти в состояние дежурного ожидания и оставаться в нем до тех пор, пока не будут обработаны все записи.*/

nDone = 0;

while (nDone < 2 * nRecord) SleepEx(INFINITE, TRUE);

CloseHandle(hInputFile);

CloseHandle(hOutputFile);

_tprintf(_T("Преобразование из ASCII в Unicode завершено.\n"));

return 0;

}

 

static VOID WINAPI ReadDone(DWORD Code, DWORD nBytes, LPOVERLAPPED pOv) {

/* Чтение завершено. Преобразовать данные и инициировать запись. */

LARGE_INTEGER CurPosIn, CurPosOut;

DWORD ic, i;

nDone++;

/* Обработать запись и инициировать операцию записи. */

ic = (DWORD)(pOv->hEvent);

CurPosIn.LowPart = OverLapIn[ic].Offset;

CurPosIn.HighPart = OverLapIn[ic].OffsetHigh;

CurPosOut.QuadPart = (CurPosIn.QuadPart / REC_SIZE) * UREC_SIZE;

OverLapOut[ic].Offset = CurPosOut.LowPart;

OverLapOut[ic].OffsetHigh = CurPosOut.HighPart;

/* Преобразовать запись из ASCII в Unicode. */

for (i = 0; i < nBytes; i++) UnRec[ic][i] = AsRec[ic][i];

WriteFileEx(hOutputFile, UnRec[ic], nBytes*2, &OverLapOut[ic], WriteDone);

/* Подготовить структуру OVERLAPPED для следующего чтения. */

CurPosIn.QuadPart += REC_SIZE * (LONGLONG)(MAX_OVRLP);

OverLapIn[ic].Offset = CurPosIn.LowPart;

OverLapIn[ic].OffsetHigh = CurPosIn.HighPart;

return;

}

 

static VOID WINAPI WriteDone(DWORD Code, DWORD nBytes, LPOVERLAPPED pOv) {

/* Запись завершена. Инициировать следующую операцию чтения. */

LARGE_INTECER CurPosIn;

DWORD ic;

nDone++;

ic = (DWORD)(pOv->hEvent);

CurPosIn.LowPart = OverLapIn[ic].Offset;

CurPosIn.HighPart = OverLapIn[ic].OffsetHigh;

if (CurPosIn.QuadPart < FileSize.QuadPart) {

ReadFileEx(hInputFile, AsRec[ic], REC_SIZE, &OverLapIn[ic], ReadDone);

}

return;

}

Асинхронный ввод/вывод сиспользованием нескольких потоков

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

Однако Windows обеспечивает многопоточную поддержку, поэтому становится возможным достижение того же эффекта за счет выполнения синхронных операций ввода/вывода в нескольких, выполняемых независимо потоках. Ранее эти возможности уже были продемонстрированы на примере многопоточных серверов и программы grepMT (глава 7). Кроме того, потоки обеспечивают концептуально последовательный и, предположительно, гораздо более простой способ выполнения асинхронных операций ввода/вывода. В качестве альтернативы методам, используемым в программах 14.1 и 14.2, можно было бы предоставить каждому потоку собственный дескриптор файла, и тогда каждый из потоков мог бы обрабатывать в синхронном режиме каждую четвертую запись.

Такой способ использования потоков продемонстрирован в программе atouMT, которая в книге не приводится, но включена в материал, размещенный на Web-сайте. Программа atouMT не только способна выполняться под управлением любой версии Windows, но и более проста по сравнению с любым из двух вариантов программ асинхронного ввода/вывода, поскольку учет использования ресурсов в этом случае менее сложен. Каждый поток просто поддерживает собственные буферы в собственном стеке и выполняет в цикле последовательность синхронных операций чтения, преобразования и записи. При этом производительность программы остается на достаточно высоком уровне.

Примечание

В программе atouMT.с, которая находится на Web-сайте, содержатся комментарии по поводу нескольких возможных "ловушек", которые могут поджидать вас при организации доступа одновременно нескольких потоков к одному и тому же файлу. В частности, все отдельные дескрипторы файлов должны создаваться с помощью функции CreateHandle, а не функции DuplicateHandle.

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

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