C++ 异步ReadDirectoryChangesW调用阻止线程退出 导言:

C++ 异步ReadDirectoryChangesW调用阻止线程退出 导言:,c++,multithreading,winapi,asynchronous,readdirectorychangesw,C++,Multithreading,Winapi,Asynchronous,Readdirectorychangesw,我正在编写一个小应用程序,用于监视某个目录中新添加的文件 我想把监控代码放在一个单独的线程中,这样我就可以让主线程自由处理其他事情,并在需要时取消监控线程 有关资料: 我用它来做监控 我正在使用原始WIN32 API创建/同步线程 我正在尝试支持Windows XP以后的版本。 问题: 我能够正确地编写所有代码,除了一件事: 我无法正确退出监控线程,因此发表了这篇文章 我在主线程中发送一个事件对象的信号,等待线程退出,然后进行清理 问题在于我对ReadDirectoryChangesW的使用,因

我正在编写一个小应用程序,用于监视某个目录中新添加的文件

我想把监控代码放在一个单独的线程中,这样我就可以让主线程自由处理其他事情,并在需要时取消监控线程

有关资料: 我用它来做监控 我正在使用原始WIN32 API创建/同步线程 我正在尝试支持Windows XP以后的版本。 问题: 我能够正确地编写所有代码,除了一件事:

我无法正确退出监控线程,因此发表了这篇文章

我在主线程中发送一个事件对象的信号,等待线程退出,然后进行清理

问题在于我对ReadDirectoryChangesW的使用,因为在我注释掉那段代码后,一切都正常

事件句柄发出信号后,ReadDirectoryChangesW将阻止线程捕获事件并退出。如果我在目录中添加一个新文件,它将取消阻止ReadDirectoryChangesW,线程将捕获事件并退出

为了进一步提供帮助,我在下面做了一个小例子,说明了我到目前为止所说的

MVCE: 我努力解决: 我已经将重叠结构从线程移到了传递给线程的结构中。然后我将OVERLAPPED.hEvent设置为强制解除阻止ReadDirectoryChangesW。这似乎有效,但让我感到害怕,因为我认为它不安全/容易出错,因为它没有文档记录

我曾尝试使用完成程序,但没有成功,因为我对这一切都是新手。我能够接收到通知,但缓冲区的内容在第一次传递后没有正确读取,缓冲区中填充了ReadDirectoryChangesW。我仍在努力让这项工作靠我自己,但可能需要帮助

我可以使用I/o完成端口,但由于我只监视一个目录,我认为这有点过分了。如果我弄错了,请告诉我如何在我的案例中使用I/o完成端口,我很乐意尝试它们

问题: 鉴于上面的MVCE,您能否指导我如何修改线程过程中的代码,使其在不阻塞ReadDirectoryChangesW的情况下正确退出

我有一种感觉,我将不得不使用完成例程。在这种情况下,我会谦虚地要求一些伪代码或书面指令,因为这将是我第一次使用它们


每次我取得进展,我都会相应地用相关数据更新这篇文章。

有3种方法可以对文件执行异步操作:

使用APC例程 使用IoCompletionPort 使用事件-最坏 您选择了最差的变体。我在你家用IoCompletionPort。在这种情况下,您不需要创建事件、线程、调用GetOverlappedResult,也不需要任何循环

所有需要调用或RtlSetIoCompletionCallback的文件和所有

关于取消-XP中不存在我正在尝试支持Windows XP , 但您可以简单地关闭目录句柄-在这种情况下,IO将被取消。所以代码可以如下所示:

RUNDOWN_REF_EVENT g_rundown; // Run-Down Protection

class SPYDATA : 
#ifdef _USE_NT_VERSION_
    IO_STATUS_BLOCK
#else
    OVERLAPPED 
#endif
{
    HANDLE _hFile;
    LONG _dwRef;
    union {
        FILE_NOTIFY_INFORMATION _fni;
        UCHAR _buf[PAGE_SIZE];
    };

    void DumpDirectoryChanges()
    {
        union {
            PVOID buf;
            PBYTE pb;
            PFILE_NOTIFY_INFORMATION pfni;
        };

        buf = _buf;

        for (;;)
        {
            DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);

            ULONG NextEntryOffset = pfni->NextEntryOffset;

            if (!NextEntryOffset)
            {
                break;
            }

            pb += NextEntryOffset;
        }
    }

#ifdef _USE_NT_VERSION_
    static VOID WINAPI _OvCompRoutine(
        _In_    NTSTATUS dwErrorCode,
        _In_    ULONG_PTR dwNumberOfBytesTransfered,
        _Inout_ PIO_STATUS_BLOCK Iosb
        )
    {
        static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
    }
#else
    static VOID WINAPI _OvCompRoutine(
        _In_    DWORD dwErrorCode, // really this is NTSTATUS
        _In_    DWORD dwNumberOfBytesTransfered,
        _Inout_ LPOVERLAPPED lpOverlapped
        )
    {
        static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
    }
#endif

    VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
    {
        DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);

        if (0 <= status) 
        {
            if (status != STATUS_NOTIFY_CLEANUP)
            {
                if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
                DoRead();
            }
            else
            {
                DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
            }
        }

        Release();
        g_rundown.ReleaseRundownProtection();
    }

    ~SPYDATA()
    {
        Cancel();
    }

public:

    void DoRead()
    {
        if (g_rundown.AcquireRundownProtection())
        {
            AddRef();
#ifdef _USE_NT_VERSION_
            NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
            if (NT_ERROR(status))
            {
                OvCompRoutine(status, 0);
            }
#else
            if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
            {
                OvCompRoutine(RtlGetLastNtStatus(), 0);
            }
#endif
        }
    }

    SPYDATA()
    {
        _hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
        _dwRef = 1;
#ifndef _USE_NT_VERSION_
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
    }

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef))
        {
            delete this;
        }
    }

    BOOL Create(POBJECT_ATTRIBUTES poa)
    {
        IO_STATUS_BLOCK iosb;
        NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
        if (0 <= status)
        {
            return
#ifdef _USE_NT_VERSION_
            0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
            BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
        }
        return FALSE;
    }

    void Cancel()
    {
        if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
        {
            NtClose(hFile);
        }
    }
};

void DemoF()
{
    if (g_rundown.Create())
    {
        STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot\\tmp");//SOME_DIRECTORY

        if (SPYDATA* p = new SPYDATA)
        {
            if (p->Create(&oa))
            {
                p->DoRead();
            }

            MessageBoxW(0, L"wait close program...", L"", MB_OK);

            p->Cancel();

            p->Release();
        }

        g_rundown.ReleaseRundownProtection();
        g_rundown.WaitForRundown();
    }
}

什么是奇怪的无止境循环事件创建/销毁的东西呢?闻起来像是货物崇拜编程。@JonathanPotter:我不知道如何正确构造程序流来处理这些错误。你能帮助解决问题中提到的实际问题吗?@JonathanPotter:我已经按照你的建议重写了代码……使用第二个事件来表示线程应该退出,并使用WaitForMultipleObjects而不是GetOverlappedResult。请注意,您的标题具有误导性。ReadDirectoryChangesW未被阻止。是GetOverlappedResult被阻止了。@RaymondChen:谢谢,如果你把你的评论作为答案,我会正式接受并投票。首先,谢谢你的回答。您选择了最差的变体。我在你家用IoCompletionPort。我认为只为1个文件夹创建IOCP并没有什么好处,只监视添加的新文件。然而,我目前正在阅读BindIoCompletionCallback的文档,直到现在我才听说过它,我觉得如果我对你的理解正确的话,你的方法看起来确实更好,我现在还不确定。请给我时间阅读您提供的链接并研究您的代码,因为我真的很想走IO完成端口路线。@AlwaysLearningNewStuff在当前情况下,什么问题会导致IOCP甚至是间接的?一个或多个不同的文件夹。创造更好的活动?此外,在事件发生时,我们没有其他上下文。如果APC或IOCP-有。因为这个事件和麦汁。以防万一,我们必须等待。在另一种情况下,我们从不等待——只需调用回调。当然,如果您以前从未使用过IOCPBindIoCompletionCallback,那么您将很难理解我的代码。当然,如果您以前从未使用过IOCPBindIoCompletionCallback,那么您的代码中的所有内容对我来说都是新的。我从公司的高级开发人员那里得到了这个任务,所以我自己做每件事。我提出了我发布的解决方案, 但我知道这不是最好的。尽管如此,我还是会努力理解您的代码,因为我同意IOCP是最好的方法,我只是不知道如何正确地实现它们。再次感谢。
RUNDOWN_REF_EVENT g_rundown; // Run-Down Protection

class SPYDATA : 
#ifdef _USE_NT_VERSION_
    IO_STATUS_BLOCK
#else
    OVERLAPPED 
#endif
{
    HANDLE _hFile;
    LONG _dwRef;
    union {
        FILE_NOTIFY_INFORMATION _fni;
        UCHAR _buf[PAGE_SIZE];
    };

    void DumpDirectoryChanges()
    {
        union {
            PVOID buf;
            PBYTE pb;
            PFILE_NOTIFY_INFORMATION pfni;
        };

        buf = _buf;

        for (;;)
        {
            DbgPrint("%x <%.*S>\n", pfni->Action, pfni->FileNameLength >> 1, pfni->FileName);

            ULONG NextEntryOffset = pfni->NextEntryOffset;

            if (!NextEntryOffset)
            {
                break;
            }

            pb += NextEntryOffset;
        }
    }

#ifdef _USE_NT_VERSION_
    static VOID WINAPI _OvCompRoutine(
        _In_    NTSTATUS dwErrorCode,
        _In_    ULONG_PTR dwNumberOfBytesTransfered,
        _Inout_ PIO_STATUS_BLOCK Iosb
        )
    {
        static_cast<SPYDATA*>(Iosb)->OvCompRoutine(dwErrorCode, (ULONG)dwNumberOfBytesTransfered);
    }
#else
    static VOID WINAPI _OvCompRoutine(
        _In_    DWORD dwErrorCode, // really this is NTSTATUS
        _In_    DWORD dwNumberOfBytesTransfered,
        _Inout_ LPOVERLAPPED lpOverlapped
        )
    {
        static_cast<SPYDATA*>(lpOverlapped)->OvCompRoutine(dwErrorCode, dwNumberOfBytesTransfered);
    }
#endif

    VOID OvCompRoutine(NTSTATUS status, DWORD dwNumberOfBytesTransfered)
    {
        DbgPrint("[%x,%x]\n", status, dwNumberOfBytesTransfered);

        if (0 <= status) 
        {
            if (status != STATUS_NOTIFY_CLEANUP)
            {
                if (dwNumberOfBytesTransfered) DumpDirectoryChanges();
                DoRead();
            }
            else
            {
                DbgPrint("\n---- NOTIFY_CLEANUP -----\n");
            }
        }

        Release();
        g_rundown.ReleaseRundownProtection();
    }

    ~SPYDATA()
    {
        Cancel();
    }

public:

    void DoRead()
    {
        if (g_rundown.AcquireRundownProtection())
        {
            AddRef();
#ifdef _USE_NT_VERSION_
            NTSTATUS status = ZwNotifyChangeDirectoryFile(_hFile, 0, 0, this, this, &_fni, sizeof(_buf), FILE_NOTIFY_VALID_MASK, TRUE);
            if (NT_ERROR(status))
            {
                OvCompRoutine(status, 0);
            }
#else
            if (!ReadDirectoryChangesW(_hFile, _buf, sizeof(_buf), TRUE, FILE_NOTIFY_VALID_MASK, (PDWORD)&InternalHigh, this, 0))
            {
                OvCompRoutine(RtlGetLastNtStatus(), 0);
            }
#endif
        }
    }

    SPYDATA()
    {
        _hFile = 0;// ! not INVALID_HANDLE_VALUE because use ntapi for open file
        _dwRef = 1;
#ifndef _USE_NT_VERSION_
        RtlZeroMemory(static_cast<OVERLAPPED*>(this), sizeof(OVERLAPPED));
#endif
    }

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef))
        {
            delete this;
        }
    }

    BOOL Create(POBJECT_ATTRIBUTES poa)
    {
        IO_STATUS_BLOCK iosb;
        NTSTATUS status = ZwOpenFile(&_hFile, FILE_GENERIC_READ, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_DIRECTORY_FILE);
        if (0 <= status)
        {
            return
#ifdef _USE_NT_VERSION_
            0 <= RtlSetIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#else
            BindIoCompletionCallback(_hFile, _OvCompRoutine, 0);
#endif
        }
        return FALSE;
    }

    void Cancel()
    {
        if (HANDLE hFile = InterlockedExchangePointer(&_hFile, 0))
        {
            NtClose(hFile);
        }
    }
};

void DemoF()
{
    if (g_rundown.Create())
    {
        STATIC_OBJECT_ATTRIBUTES(oa, "\\systemroot\\tmp");//SOME_DIRECTORY

        if (SPYDATA* p = new SPYDATA)
        {
            if (p->Create(&oa))
            {
                p->DoRead();
            }

            MessageBoxW(0, L"wait close program...", L"", MB_OK);

            p->Cancel();

            p->Release();
        }

        g_rundown.ReleaseRundownProtection();
        g_rundown.WaitForRundown();
    }
}
class __declspec(novtable) RUNDOWN_REF
{
    LONG _LockCount;

protected:

    virtual void RundownCompleted() = 0;

public:

    RUNDOWN_REF()
    {
        _LockCount = 1;
    }

    BOOL AcquireRundownProtection()
    {
        LONG LockCount = _LockCount, prevLockCount;

        do 
        {
            if (!LockCount)
            {
                return FALSE;
            }

            LockCount = InterlockedCompareExchange(&_LockCount, LockCount + 1, prevLockCount = LockCount);

        } while (LockCount != prevLockCount);

        return TRUE;
    }

    void ReleaseRundownProtection()
    {
        if (!InterlockedDecrement(&_LockCount))
        {
            RundownCompleted();
        }
    }
};

class RUNDOWN_REF_EVENT : public RUNDOWN_REF
{
    HANDLE _hEvent;

    virtual void RundownCompleted()
    {
        SetEvent(_hEvent);
    }

public:

    BOOL Create()
    {
        return (_hEvent = CreateEvent(0, TRUE, FALSE, 0)) != 0;
    }

    RUNDOWN_REF_EVENT()
    {
        _hEvent = 0;
    }

    ~RUNDOWN_REF_EVENT()
    {
        if (_hEvent) CloseHandle(_hEvent);
    }

    void WaitForRundown()
    {
        if (WaitForSingleObject(_hEvent, INFINITE) != WAIT_OBJECT_0) __debugbreak();
    }
};