Если вы ожидаете завершения операции ввода-вывода, бывает полезным сначала запустить эту операцию ввода-вывода

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

Клиент сообщил о зависании в функции GetOverlappedResult, которая ожидала отмены операции ввода-вывода, и к расследованию подключилась команда разработки подсистемы ввода-вывода. Они проанализировали стек ввода-вывода и обнаружили, что операция ввода-вывода, которую ожидал клиент, на момент ожидания более не была активной. Члены команды составили список возможных причин:

  • операция ввода-вывода изначально была активной, но после того, как она завершилась, ошибка в драйвере воспрепятствовала срабатыванию события;
  • операция ввода-вывода изначально была активной, а затем завершилась, но программа непреднамеренно вызвала функцию ResetEvent на дескрипторе события, сведя на нет результаты операции SetEvent, выполненной подсистемой ввода-вывода;
  • операция ввода-вывода никогда не была активной.

Возможные причины расположены в порядке возрастания вероятности (и, скорее всего, ненамеренно, в порядке убывания отношения к команде разработчиков подсистемы).

Более детальное изучение кода клиента выявило путь выполнения, при котором операция ReadFile не выполнялась. Затем программа возвращалась в основную ветвь выполнения, некоторое время работала, а потом решала, что она уже устала ждать, когда эта операция чтения завершится, и вызывала функцию CancelIo, предваряющую вызов GetOverlappedResult, чтобы дождаться окончания отмены операции.

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

Ладно, может быть, это была ошибка, допущенная в спешке или по невнимательности. Но вот, к примеру, такой вариант, наверняка, менее очевиден:

// в этом коде есть ошибка — смотрите пояснения далее<br>// предположим, что мы выполняем операцию над файлом с флагом FILE_FLAG_OVERLAPPED<br>if (ReadFile(h, ..., &overlapped)) {<br> // операция ввода-вывода выполнилась синхронно, этому мы уже научились ранее<br>} else {<br> // операция ввода-вывода все еще выполняется<br> ... делаем что-нибудь ...<br> // ладно, давайте, дождемся окончания выполнения этой операции ввода-вывода<br> GetOverlappedResult(h, &overlapped, &dwRead, TRUE);<br> ...<br>}

Вызов функции GetOverlappedResult может привести к зависанию, поскольку комментарий «операция ввода-вывода все еще выполняется» чересчур оптимистичный: операция ввода-вывода могла и вовсе не начаться. Если она не начиналась, то она никогда и не закончится. Вы не можете предполагать, что значение FALSE, которое вернула функция ReadFile, означает, что операция ввода-вывода все еще выполняется. Вам также нужно проверить, что GetLastError() возвращает значение ERROR_IO_PENDING. В противном случае, во время запуска операции ввода-вывода произошла ошибка и вам не нужно ждать ее завершения.

// предположим, что мы выполняем операцию над файлом с флагом FILE_FLAG_OVERLAPPED if (ReadFile(h, ..., &overlapped)) {   // операция ввода-вывода выполнилась синхронно, этому мы уже научились ранее } else if (GetLastError() == ERROR_IO_PENDING) {   // операция ввода-вывода все еще выполняется   ... делаем что-нибудь ...   // ладно, давайте, дождемся окончания выполнения этой операции ввода-вывода    GetOverlappedResult(h, &overlapped, &dwRead, TRUE);    ... } else {<br> // во время запуска операции ввода-вывода произошла ошибка — не ждите ее завершения, потому что ждать, собственно, нечего!<br>}