在循环中异步使用ReadDirectoryChangesW

时间:2016-11-14 16:31:37

标签: c++ winapi asynchronous readdirectorychangesw

引言:

我试图在循环中异步使用ReadDirectoryChangesW

以下代码段说明了我要实现的目标:

DWORD example()
{
    DWORD error = 0;

    OVERLAPPED ovl = { 0 };
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == ovl.hEvent) return ::GetLastError();

    char buffer[1024];

    while(1)
    {
        process_list_of_existing_files();

        error = ::ReadDirectoryChangesW(
            m_hDirectory, // I have added FILE_FLAG_OVERLAPPED in CreateFile
            buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL);

        // we have new files, append them to the list
        if(error) append_new_files_to_the_list(buffer);
        // just continue with the loop
        else if(::GetLastError() == ERROR_IO_PENDING) continue;
        // RDCW error, this is critical -> exit
        else return ::GetLastError(); 
    }
}

问题:

我不知道当ReadDirectoryChangesW返回FALSEGetLastError()代码为ERROR_IO_PENDING时,如何处理此案例。

在这种情况下,我应该继续循环并继续循环,直到ReadDirectoryChangesW返回buffer我可以处理。

我的努力解决了这个问题:

我尝试使用WaitForSingleObject(ovl.hEvent, 1000),但崩溃时遇到错误1450 ERROR_NO_SYSTEM_RESOURCES。下面是重现此行为的MVCE:

#include <iostream>
#include <Windows.h>

DWORD processDirectoryChanges(const char *buffer)
{
    DWORD offset = 0;
    char fileName[MAX_PATH] = "";
    FILE_NOTIFY_INFORMATION *fni = NULL;

    do
    {
        fni = (FILE_NOTIFY_INFORMATION*)(&buffer[offset]);
        // since we do not use UNICODE, 
        // we must convert fni->FileName from UNICODE to multibyte
        int ret = ::WideCharToMultiByte(CP_ACP, 0, fni->FileName,
            fni->FileNameLength / sizeof(WCHAR),
            fileName, sizeof(fileName), NULL, NULL);

        switch (fni->Action)
        {
        case FILE_ACTION_ADDED:     
        {
            std::cout << fileName << std::endl;
        }
        break;
        default:
            break;
        }

        ::memset(fileName, '\0', sizeof(fileName));
        offset += fni->NextEntryOffset;

    } while (fni->NextEntryOffset != 0);

    return 0;
}

int main()
{
    HANDLE hDir = ::CreateFile("C:\\Users\\nenad.smiljkovic\\Desktop\\test", 
        FILE_LIST_DIRECTORY,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL, OPEN_EXISTING, 
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

    if (INVALID_HANDLE_VALUE == hDir) return ::GetLastError();

    OVERLAPPED ovl = { 0 };
    ovl.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL);

    if (NULL == ovl.hEvent) return ::GetLastError();

    DWORD error = 0, br;
    char buffer[1024];

    while (1)
    {
        error = ::ReadDirectoryChangesW(hDir,
            buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL);

        if (0 == error)
        {
            error = ::GetLastError();

            if (ERROR_IO_PENDING != error)
            {
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }
        }

        error = ::WaitForSingleObject(ovl.hEvent, 0);

        switch (error)
        {
        case WAIT_TIMEOUT:
            break;
        case WAIT_OBJECT_0:
        {
            error = processDirectoryChanges(buffer);

            if (error > 0)
            {
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }

            if (0 == ::ResetEvent(ovl.hEvent))
            {
                error = ::GetLastError();
                ::CloseHandle(ovl.hEvent);
                ::CloseHandle(hDir);
                return error;
            }
        }
        break;
        default:
            error = ::GetLastError();
            ::CloseHandle(ovl.hEvent);
            ::CloseHandle(hDir);
            return error;
            break;
        }
    }

    return 0;
}

阅读文档,似乎我需要将GetOverlappedResult的最后一个参数设置为FALSE,但我不知道如何正确使用此API。

问题:

由于MVCE很好地说明了我要做的事情(打印新添加文件的名称),你能告诉我在while循环中必须修复哪些内容才能使它工作? / p>

同样,重点是在循环中异步使用ReadDirectoryChangesW,如简介中的代码段所示。

2 个答案:

答案 0 :(得分:3)

程序的基本结构或多或少看起来不错,您只是错误地使用异步I / O调用。每当没有新文件时,事件句柄上的等待会立即超时,这很好,但是您会发出一个全新的I / O请求,但不是。

这就是为什么你的系统资源不足的原因;您无需等待其中任何一个完成即可完全发出I / O请求。您应该只在现有请求完成后发出新请求

(另外,你应该调用GetOverlappedResult来检查I / O是否成功。)

所以你的循环看起来应该更像这样:

    ::ReadDirectoryChangesW(hDir,
        buffer, sizeof(buffer), FALSE,
        FILE_NOTIFY_CHANGE_FILE_NAME,
        NULL, &ovl, NULL);

    while (1)
    {
        DWORD result = ::WaitForSingleObject(ovl.hEvent, 0);

        switch (result)
        {
        case WAIT_TIMEOUT:

            processBackgroundTasks();

            break;

        case WAIT_OBJECT_0:

            ::GetOverlappedResult(hDir, &ovl, &dw, FALSE);

            processDirectoryChanges(buffer);

            ::ResetEvent(ovl.hEvent);

            ::ReadDirectoryChangesW(hDir,
                buffer, sizeof(buffer), FALSE,
                FILE_NOTIFY_CHANGE_FILE_NAME,
                NULL, &ovl, NULL);

            break;
        }
    }

注意:

  • 为简单起见,错误处理已被省略;我没有进行任何测试或检查您的代码是否有任何其他问题。

  • 如果可能没有任何后台任务要执行,您应该测试该情况并将超时设置为INFINITE而不是0,否则您将会旋转。

  • 我只希望显示使其工作所需的最小变化,但调用WaitForSingleObject后跟GetOverlappedResult是多余的;对GetOverlappedResult的单次调用都可以检查I / O是否完整,如果是,则检索结果。

根据要求,修改后的版本仅使用GetOverlappedResult并且具有最小的错误检查。我还添加了一个例子,说明如何处理你没有工作的情况;如果您对文件进行的任何处理确实真的一直在运行,那么您就不需要这样了。

    ::ResetEvent(ovl.hEvent);

    if (!::ReadDirectoryChangesW(hDir,
        buffer, sizeof(buffer), FALSE,
        FILE_NOTIFY_CHANGE_FILE_NAME,
        NULL, &ovl, NULL))
    {
       error = GetLastError();
       if (error != ERROR_IO_PENDING) fail();
    }

    while (1)
    {
        BOOL wait;

        result = process_list_of_existing_files();

        if (result == MORE_WORK_PENDING)
        {
           wait = FALSE;
        } 
        else if (result == NO_MORE_WORK_PENDING)
        {
           wait = TRUE;
        } 

        if (!::GetOverlappedResult(hDir, &ovl, &dw, wait))
        {
           error = GetLastError();
           if (error == ERROR_IO_INCOMPLETE) continue;
           fail();
        }

        processDirectoryChanges(buffer);

        ::ResetEvent(ovl.hEvent);

        if (!::ReadDirectoryChangesW(hDir,
            buffer, sizeof(buffer), FALSE,
            FILE_NOTIFY_CHANGE_FILE_NAME,
            NULL, &ovl, NULL))
        {
           error = GetLastError();
           if (error != ERROR_IO_PENDING) fail();
        } 
    }

答案 1 :(得分:1)

间接使用IOCP的变体

  1. 创建继承(包含)OVERLAPPED(或。)的类/结构 IO_STATUS_BLOCK),引用计数,目录句柄和数据 你需要
  2. 致电BindIoCompletionCallbackRtlSetIoCompletionCallback) 目录句柄,用于设置回调
  3. DoRead()例程,我们第一次从主线程调用 然后从回调
  4. 每次拨打DoRead()电话之前,{li}都在ReadDirectoryChangesW AddRef();因为我们将引用(跨越OVERLAPPED)传递给我们 struct to kernel
  5. main(比如GUI线程)可以在初始调用后继续执行自己的任务 DoRead(),与APC变体不同,他不需要等待处于警戒状态
  6. 在回调中我们从继承(包含)获得了指向我们的struct的指针 重叠。如果需要,可以执行任何任务(processDirectoryChanges) 继续间谍 - 调用DoRead(),最后调用Release()
  7. 如果来自ReadDirectoryChangesW的{​​{1}}失败(结果将无法回调) - 我们需要直接回拨 错误代码
  8. 停止我们可以简单地关闭目录句柄 - 结果我们得到了 回调中的STATUS_NOTIFY_CLEANUP
  9. ==================================

    DoRead()