3

前書き:

ループ内で非同期に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返さFALSEれた場合の処理​​方法がわかりません。GetLastError()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ループを機能させるためにループで何を修正する必要があるかを教えてもらえますか?

繰り返しますが、要点はReadDirectoryChangesW、INTRODUCTION のスニペットに示されているように、ループ内で非同期に使用することです。

4

2 に答える 2

5

プログラムの基本的な構造は多かれ少なかれ問題ないように見えますが、非同期 I/O 呼び出しを間違って使用しているだけです。新しいファイルがない場合はいつでも、イベント ハンドルの待機がすぐにタイムアウトしますが、これは問題ありませんが、その後、まったく新しい 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 dw;
        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;
        }
    }

ノート:

  • 簡単にするために、エラー処理は省略されています。私はテストを行っておらず、他の問題についてコードをチェックしていません。

  • 実行するバックグラウンド タスクがない可能性がある場合は、そのケースをテストし、発生時にタイムアウトを 0 ではなく INFINITE に設定する必要があります。そうしないと、スピンが発生します。

  • 機能させるために必要な最小限の変更のみを示したかったのですが、WaitForSingleObject を呼び出してから GetOverlappedResult を呼び出すのは冗長です。GetOverlappedResult を 1 回呼び出すだけで、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();
        } 
    }
于 2016-11-14T22:51:48.143 に答える