IOCP ReadFile 始终阻塞直到读取完成

IOCP ReadFile always blocks until read completion

这是使用 iocp 读取文件的示例源。

应该立即返回,因为它在调用ReadFile时进行了异步调用,而ReadFile似乎是同步工作的。

有什么问题?

他的测试环境是visual studio2017 enterprise,windwos 10, windows sdk版本为10.0.17763.0.

#define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1)

const int BUFFERSIZE = 1024 * 1024 * 400;
BYTE ReadBuffer[BUFFERSIZE] = { 0 };

DWORD WINAPI WaitQueue(LPVOID lpParam)
{
    auto hIocp = (HANDLE)lpParam;

    // WAIT COMPLETION QUEUE
    DWORD numberOfBytes;
    ULONG_PTR val;

    LPOVERLAPPED ov = { 0 };

    for (;;)
    {
        BOOL bSuccess = GetQueuedCompletionStatus(hIocp, &numberOfBytes, (PULONG_PTR)&val, &ov, INFINITE);

        SYSTEMTIME dequeTime;
        GetSystemTime(&dequeTime);

        Sleep(1000);

        printf("dequeue time %dsec %dmilli", dequeTime.wSecond, dequeTime.wMilliseconds);
    }
}

DWORD WINAPI ReadFileThread(LPVOID lpParam)
{
    Sleep(3000);

    auto hIocp = (HANDLE)lpParam;

    // CREATE FILE HANDLE
    auto fileName = "e:\test.msi";

    auto hFile = CreateFile(fileName,
        FILE_READ_DATA,
        FILE_SHARE_READ,
        0,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        0);

    if (hFile == INVALID_HANDLE_VALUE)
    {
        std::wcout << L"create file fail - " << fileName << std::endl;
        return 0;
    }

    // REGIST FILE HANDLE TO IOCP
    if (hIocp != CreateIoCompletionPort(hFile, hIocp, 0, 2))
    {
        auto err = GetLastError();

        std::cout << "add file handle fail:" << err << " - file handle:" << hIocp << std::endl;

        CloseHandle(hFile);
        CloseHandle(hIocp);
        return 0;
    }

    // READ FILE
    OVERLAPPED ol = { 0 };

    SYSTEMTIME startTime;
    GetSystemTime(&startTime);

    if (FALSE == ReadFile(hFile, ReadBuffer, _countof(ReadBuffer), 0, &ol))
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
            printf("Terminal failure: Unable to read from file.\n GetLastError=%08x\n", GetLastError());
            CloseHandle(hFile);
            return -1;
        }
    }

    DWORD d;
    GetOverlappedResult(hFile, &ol, &d, true);

    SYSTEMTIME endTime;
    GetSystemTime(&endTime);

    printf("start time %dsec %dmilli", startTime.wSecond, startTime.wMilliseconds);
    printf("end time %dsec %dmilli", endTime.wSecond, endTime.wMilliseconds);
}

int main()
{
    // CREATE ICOP
    auto hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1);

    if (hIocp == NULL)
    {
        auto err = GetLastError();

        std::cout << "Create IOCP failed. error:" << err << std::endl;
        return 0;
    }

    // CREATE READ THREAD
    CreateThread(
        NULL,
        0,
        ReadFileThread,
        hIocp,
        0,
        nullptr
    );

    // CREATE WAIT DEQUEUE THREAD
    CreateThread(
        NULL,
        0,
        WaitQueue,
        hIocp,
        0,
        nullptr
    );

    while (true)
    {
    }

    return 0;
}

首先这里的iocp是绝对不相关的。您尝试立即或阻止异步文件句柄 return 上的 I/O 操作(在您的情况下读取)。 iocp 这里有什么关系?将 iocp 绑定到文件是获得通知的唯一方式,当 I/O 完成时。但这绝对不影响 I/O 自己阻止或 return 立即。我们可以在这里使用任何通知方式。 (apc、iocp 或事件)。为了您的测试目的,最简单的使用事件。

那么让我们看看 - 您如何测试 - 在您的代码中是读取块还是 return?你根本不测试这个。在 ReadFile return 之后测试此需求 - 操作是否完成。 I/O 操作 (ReadFile) 是异步完成的 - 如果 api 已经调用 return 控制给你,但是 OVERLAPPED(IO_STATUS_BLOCK ) 还没有被系统更新,这意味着 I/O 仍未完成。 OVERLAPPED 是否已更新我们可以直接检查(OVERLAPPED 结构的 Internal 成员是 不是 STATUS_PENDING)或通过调用 GetOverlappedResult bWait 设置为 FALSE

If this parameter is FALSE and the operation is still pending, the function returns FALSE and the GetLastError function returns ERROR_IO_INCOMPLETE.

所以如果接下来的 4 个条件为真,我们可以说 ReadFile 异步完成:

  1. ReadFile return FALSE
  2. GetLastError() return ERROR_IO_PENDING
  3. GetOverlappedResult(.., FALSE) return FALSE
  4. GetLastError() return ERROR_IO_INCOMPLETE

你没有在自己的代码中检查这个。相反,你要等到 io 操作通过 GetOverlappedResult(.., true) 完全完成,然后花点时间。这样做有什么意义?

实际测试代码:

void tt(PCWSTR FileName)
{
    HANDLE hFile = CreateFile(FileName, FILE_GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        OVERLAPPED ov {};
        if (ov.hEvent = CreateEvent(0, 0, 0, 0))
        {
            char buf[1024];
            if (ReadFile(hFile, buf, sizeof(buf), 0, &ov))
            {
                DbgPrint("sync(#1)\n");
            }
            else
            {
                switch (GetLastError())
                {
                case ERROR_IO_PENDING:
                    ULONG n;
                    if (GetOverlappedResult(hFile, &ov, &n, FALSE))
                    {
                        DbgPrint("sync(#2)\n");
                    }
                    else 
                    {
                        switch (GetLastError())
                        {
                        case ERROR_IO_INCOMPLETE:
                            DbgPrint("async\n");
                            if (!GetOverlappedResult(hFile, &ov, &n, TRUE))
                            {
                                __debugbreak();
                            }
                            break;
                        default: __debugbreak();
                        }
                    }
                    break;
                default: __debugbreak();
                }
            }
            CloseHandle(ov.hEvent);
        }
        CloseHandle(hFile);
    }
}

请注意,读取完成的结果(同步或非同步)取决于缓存中的文件数据。如果你为文件调用它,之前没有读取它(所以数据不在缓存中)可能 api 打印 "async",但是如果你再次为同一个文件调用这个函数 - 你下次会更快(几乎 100%)将查看 "sync(#2)"