C++ 异步ReadDirectoryChangesW调用阻止线程退出 导言:
我正在编写一个小应用程序,用于监视某个目录中新添加的文件 我想把监控代码放在一个单独的线程中,这样我就可以让主线程自由处理其他事情,并在需要时取消监控线程 有关资料: 我用它来做监控 我正在使用原始WIN32 API创建/同步线程 我正在尝试支持Windows XP以后的版本。 问题: 我能够正确地编写所有代码,除了一件事: 我无法正确退出监控线程,因此发表了这篇文章 我在主线程中发送一个事件对象的信号,等待线程退出,然后进行清理 问题在于我对ReadDirectoryChangesW的使用,因为在我注释掉那段代码后,一切都正常 事件句柄发出信号后,ReadDirectoryChangesW将阻止线程捕获事件并退出。如果我在目录中添加一个新文件,它将取消阻止ReadDirectoryChangesW,线程将捕获事件并退出 为了进一步提供帮助,我在下面做了一个小例子,说明了我到目前为止所说的 MVCE: 我努力解决: 我已经将重叠结构从线程移到了传递给线程的结构中。然后我将OVERLAPPED.hEvent设置为强制解除阻止ReadDirectoryChangesW。这似乎有效,但让我感到害怕,因为我认为它不安全/容易出错,因为它没有文档记录 我曾尝试使用完成程序,但没有成功,因为我对这一切都是新手。我能够接收到通知,但缓冲区的内容在第一次传递后没有正确读取,缓冲区中填充了ReadDirectoryChangesW。我仍在努力让这项工作靠我自己,但可能需要帮助 我可以使用I/o完成端口,但由于我只监视一个目录,我认为这有点过分了。如果我弄错了,请告诉我如何在我的案例中使用I/o完成端口,我很乐意尝试它们 问题: 鉴于上面的MVCE,您能否指导我如何修改线程过程中的代码,使其在不阻塞ReadDirectoryChangesW的情况下正确退出 我有一种感觉,我将不得不使用完成例程。在这种情况下,我会谦虚地要求一些伪代码或书面指令,因为这将是我第一次使用它们C++ 异步ReadDirectoryChangesW调用阻止线程退出 导言:,c++,multithreading,winapi,asynchronous,readdirectorychangesw,C++,Multithreading,Winapi,Asynchronous,Readdirectorychangesw,我正在编写一个小应用程序,用于监视某个目录中新添加的文件 我想把监控代码放在一个单独的线程中,这样我就可以让主线程自由处理其他事情,并在需要时取消监控线程 有关资料: 我用它来做监控 我正在使用原始WIN32 API创建/同步线程 我正在尝试支持Windows XP以后的版本。 问题: 我能够正确地编写所有代码,除了一件事: 我无法正确退出监控线程,因此发表了这篇文章 我在主线程中发送一个事件对象的信号,等待线程退出,然后进行清理 问题在于我对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();
}
};