IOCP ReadFile всегда блокируется до завершения чтения - PullRequest
0 голосов
/ 16 января 2019

Это пример источника для чтения файлов с использованием 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 голосов
/ 16 января 2019

прежде всего iocp здесь абсолютно не связано. Вы пытаетесь проверить, являются ли операции ввода-вывода (читаются в вашем случае) на дескрипторе асинхронного файла немедленным возвращением или блокированием как здесь связано iocp ? привязка iocp к файлу - единственный способ получить уведомление, когда ввод / вывод завершен. но это абсолютно не влияет на сам блок ввода / вывода или немедленный возврат. мы можем использовать любой способ уведомления здесь. (APC, IOCP или событие). для вашего теста наиболее просто использовать событие.

тогда давайте посмотрим как - как вы тестируете - читается блок или возвращается в ваш код? Вы не проверяете это вообще. проверить эту потребность после ReadFile возврата - завершена операция или нет. операция ввода / вывода (ReadFile) завершается асинхронно - если вызов API уже возвращает вам управление, но OVERLAPPED (IO_STATUS_BLOCK) еще не обновлен системой, что означает, что ввод / вывод еще не завершен. 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

Вы не проверяете это в своем коде. вместо этого вы ждете, пока операция 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)"

...