首页 > 解决方案 > IOCP ReadFile 总是阻塞直到读取完成

问题描述

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

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

问题是什么?

测试环境为visual studio 2017 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;
}

标签: readfileiocp

解决方案


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

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

如果此参数为FALSE且操作仍处于挂起状态,则函数返回FALSEGetLastError函数返回 ERROR_IO_INCOMPLETE

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

  1. ReadFile返回FALSE
  2. GetLastError()返回ERROR_IO_PENDING
  3. GetOverlappedResult(.., FALSE)返回FALSE
  4. GetLastError()返回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 打印“异步”,但如果您再次为同一个文件调用此函数 - 您下次更快(几乎 100%)将查看“同步(#2)”


推荐阅读