使用 GetOverlappedResultEx 中的超时来模拟超时等待?

Using the timeout in GetOverlappedResultEx to simulate a wait with timeout?

当使用 GetOverlapedResult 获取重叠(即异步)I/O 操作的结果时,您可以请求 GetOverlappdResult "wait":

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

这里要注意的部分是GetOverlappedResult的最后一个参数:bWait:

bWait

If this parameter is TRUE, and the Internal member of the lpOverlapped structure is STATUS_PENDING, the function does not return until the operation has been completed. If this parameter is FALSE and the operation is still pending, the function returns FALSE and the GetLastError function returns ERROR_IO_INCOMPLETE.

对于我的代码,这意味着:

这一切都很好。

大多数情况下这一切都很好

我遇到一个问题,实际 ReadFile 操作需要 10 分钟 到 return .当它 return 时,它 returns:

The device is not ready (21)

这是一个众所周知的问题 one vendor's storage system.

我想做的是使用 Windows 的 异步 功能来等待 - 但有超时。

我注意到 GetOverlappedResultEx,它有一种 timeout 参数:

BOOL GetOverlappedResultEx(
  HANDLE       hFile,
  LPOVERLAPPED lpOverlapped,
  LPDWORD      lpNumberOfBytesTransferred,
  DWORD        dwMilliseconds, <----------
  BOOL         bAlertable
);

但是当我查看文档时,这是我了解 Windows 我不理解的编程细节的地方 - 排队的 APC,可变等待。但它仍然听起来像我想要的那样::

dwMilliseconds

The time-out interval, in milliseconds.

If dwMilliseconds is zero and the operation is still in progress, the function returns immediately and the GetLastError function returns ERROR_IO_INCOMPLETE.

If dwMilliseconds is nonzero and the operation is still in progress, the function waits until the object is signaled, an I/O completion routine or APC is queued, or the interval elapses before returning. Use GetLastError to get extended error information.

If dwMilliseconds is INFINITE, the function returns only when the object is signaled or an I/O completion routine or APC is queued.

所以我尝试改变我的功能:

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      //if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, False) //wait 5000 ms
         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

除了对 GetOverlappedResultEx 的调用不会在 5 秒(5,000 毫秒)内 return。相反,存储子系统需要 10-20 分钟才能解决 return 故障。

所以我随机尝试一些东西

我看到 GetOverlappedResultsEx 的另一个参数:

`bAlertable`

If this parameter is **TRUE** and the calling thread is in the waiting state, the function returns when the system queues an I/O completion routine or APC. The calling thread then runs the routine or function. Otherwise, the function does not return, and the completion routine or APC function is not executed.

A completion routine is queued when the [ReadFileEx][5] or [WriteFileEx][5] function in which it was specified has completed. The function returns and the completion routine is called only if *bAlertable* is **TRUE**, and the calling thread is the thread that initiated the read or write operation. An APC is queued when you call [QueueUserAPC][5].

听起来不像我的情况:

但这并不能阻止我随机尝试并希望它们起作用:

DWORD le = ERROR_SUCCESS; //lastError = 0

if (!ReadFile(FSourceDiskHandle, buffer, BUFFER_SIZE, out bytesRead, overlapped)
{
   //The read operation did not complete synchronously. See if it's still pending.
   le = GetLastError;
   if (le == ERROR_IO_PENDING)
   {
      le = ERROR_SUCCESS;
      //if (!GetOverlappedResult(FSourceDiskHandle, overlapped, out bytesRead, true) // <---bWait = true 
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, False) //wait 5000 ms
      if (!GetOverlappedResultEx(FSourceDiskHandle, overlapped, out bytesRead, 5000, True) //wait 5000 ms, alertable

         le = GetLastError;
   }

   if (le != ERROR_SUCCESS)
      LogFmt("Error reading source: %s (%d)', SysErrorMessage(le), le], TRACE_LEVEL_ERROR);
}

但是没用。

所以我问 Whosebug

我可以使用 GetOverlappedResultEx 模拟同步 ReadFile 操作但 超时吗?

我确定我最终会遇到一个涉及消息传递计时器的漏洞黑客攻击(或者是线程计时器?还是可警报计时器?和事件?和我自己的事件?或者重叠的事件?)。但我宁愿使用 good 解决方案而不是 my 解决方案。

Can i simulate a synchronous ReadFile operation but with a timeout, using GetOverlappedResultEx?

是的,你可以,就像你一样,已经尝试过了。这不是模拟。这将是完全同步的文件读取。因为同步读取 - 这是异步读取 + 在 I/O 完成时就地等待。所以代码可以是下一个:

ULONG ReadFileTimeout(HANDLE hFile, 
               PVOID lpBuffer, 
               ULONG nNumberOfBytesToRead, 
               PULONG lpNumberOfBytesRead, 
               PLARGE_INTEGER ByteOffset, 
               ULONG dwMilliseconds)
{
    OVERLAPPED ov;
    ov.Offset = ByteOffset->LowPart;
    ov.OffsetHigh = ByteOffset->HighPart;

    ULONG dwError = NOERROR;

    if (ov.hEvent = CreateEvent(0, 0, 0, 0))
    {
        dwError = ReadFile(hFile, lpBuffer, nNumberOfBytesToRead, lpNumberOfBytesRead, &ov) ? NOERROR : GetLastError();

        if (dwError == ERROR_IO_PENDING)
        {
            dwError = GetOverlappedResultEx(0/*yes, not need hFile*/, 
                &ov, lpNumberOfBytesRead, dwMilliseconds, FALSE) ? NOERROR : GetLastError();
        }

        CloseHandle(ov.hEvent);
    }
    else
    {
        dwError = GetLastError();
    }

    return dwError;
}

请注意,您必须(在异步文件的情况下)始终显式使用字节偏移量,从那里读取数据。

GetOverlappedResultEx 通常下一步 - 检查 OVERLAPPED - I/O 是否完整(只需将 InternalSTATUS_PENDING 进行比较),如果没有(仍然是 Internal == STATUS_PENDING)并且你想要等待 - 它从 OVERLAPPED 调用 WaitForSingleObjectExhEvent 并转移 dwMillisecondsbAlertable。如果 hEvent 发出信号(WAIT_OBJECT_0 return 来自 WaitForSingleObjectExGetOverlappedResultEx 决定 I/O 完成并且 return 来自 [=14] =], 否则相应错误 returned.

但万一 ReadFile 本身不是 return 控制和等待 - 这里不可能做某事。这意味着驱动程序开始等待您的线程。并且没有任何办法打破这个驱动程序等待。看起来这正是你的情况。当你使用异步文件时 - 驱动程序不能等待,但..有时(糟糕的驱动程序)这可能是