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


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

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

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

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

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

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

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

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

Вызов функции 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 {
  // во время запуска операции ввода-вывода произошла ошибка — не ждите ее завершения, потому что ждать, собственно, нечего!
}


Skip to main content