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

时间:2019-01-16 11:56:23

标签: readfile iocp

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

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

出什么问题了?

测试环境是Visual Studio 2017企业版,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;
}

1 个答案:

答案 0 :(得分:1)

首先,

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

然后让我们看起来像-如何测试-被阻止还是返回代码?您根本不会测试。在ReadFile返回之后测试此需求-操作是否完成。 I / O操作(ReadFile)是异步完成的-如果api调用已将控制权返回给您,但是OVERLAPPEDIO_STATUS_BLOCK)尚未由系统更新,则意味着I / O仍未完成。已OVERLAPPED更新了,我们可以直接检查(Internal结构的OVERLAPPED成员是不是 STATUS_PENDING)或通过调用GetOverlappedResult bWait 设置为 FALSE

  

如果此参数为FALSE,并且操作仍在进行中,则   函数返回FALSE,而GetLastError函数返回   ERROR_IO_INCOMPLETE

所以我们可以说ReadFile是异步的,如果下4个编码为真:

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

您未在自己的代码中对此进行检查。相反,您要等到GetOverlappedResult(.., true)的io操作完全完成,然后再花点时间。这有什么意义?

要测试的真实代码:

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%)查看“ sync(#2)”