ReadDirectoryChangesW回调从未触发,程序卡在WaitForSingleObject求助
问题分析与修复
你的代码卡在WaitForSingleObject且回调不触发的核心原因是:使用异步IO回调时,线程需要进入可警告等待状态才能处理APC(异步过程调用),而WaitForSingleObject不支持处理APC,导致回调永远无法被调用,事件也不会被触发。
关键问题点
ReadDirectoryChangesW指定回调函数后,系统会将回调作为APC放入线程队列,只有线程处于可警告等待(alertable wait)时才会执行这个APC。WaitForSingleObject是不可警告的阻塞函数,会一直卡住,永远不会处理队列里的APC回调。
修复步骤
- 将
WaitForSingleObject替换为WaitForSingleObjectEx,并将最后一个参数设为TRUE,让线程进入可警告等待状态,这样APC回调就能被执行。 - 在回调函数中重新调用
ReadDirectoryChangesW,实现持续监控(否则只能捕获一次目录变化)。 - 可选优化:如果使用回调函数,
OVERLAPPED的hEvent可以不用创建,简化代码。
修改后的完整代码
#include <windows.h> #include <wininet.h> #include <shlwapi.h> #pragma comment(lib, "wininet.lib") #pragma comment(lib, "shlwapi.lib") #include <string> // 全局变量保存监控所需的句柄和缓冲区,方便回调中复用 HANDLE g_hDirectory = INVALID_HANDLE_VALUE; BYTE g_buffer[4096]; OVERLAPPED g_overlapped = {}; VOID CALLBACK DirectoryChangeCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped) { if (dwErrorCode != ERROR_SUCCESS) { return; } // 解析目录变化通知 FILE_NOTIFY_INFORMATION* pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(g_buffer); do { wchar_t fileName[MAX_PATH] = {0}; wcsncpy_s(fileName, pNotify->FileName, pNotify->FileNameLength / sizeof(wchar_t)); switch (pNotify->Action) { case FILE_ACTION_ADDED: wprintf(L"文件添加: %s\n", fileName); break; case FILE_ACTION_REMOVED: wprintf(L"文件删除: %s\n", fileName); break; case FILE_ACTION_MODIFIED: wprintf(L"文件修改: %s\n", fileName); break; case FILE_ACTION_RENAMED_OLD_NAME: wprintf(L"文件重命名(旧): %s\n", fileName); break; case FILE_ACTION_RENAMED_NEW_NAME: wprintf(L"文件重命名(新): %s\n", fileName); break; } pNotify = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(reinterpret_cast<BYTE*>(pNotify) + pNotify->NextEntryOffset); } while (pNotify->NextEntryOffset != 0); // 重新发起监控,实现持续监听 DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE; if (!ReadDirectoryChangesW(g_hDirectory, g_buffer, sizeof(g_buffer), TRUE, dwNotifyFilter, NULL, &g_overlapped, DirectoryChangeCallback)) { if (GetLastError() != ERROR_IO_PENDING) { // 可在此添加错误处理逻辑 } } } int main() { std::wstring path = L"C:\\Temp"; // 打开目录句柄 g_hDirectory = CreateFileW(path.c_str() , GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE , NULL , OPEN_EXISTING , FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED , NULL); if (g_hDirectory == INVALID_HANDLE_VALUE) { wprintf(L"打开目录失败,错误码: %d\n", GetLastError()); return 1; } // 初始化重叠结构(使用回调时无需设置hEvent) ZeroMemory(&g_overlapped, sizeof(g_overlapped)); // 第一次发起监控 DWORD dwNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE; if (!ReadDirectoryChangesW(g_hDirectory, g_buffer, sizeof(g_buffer), TRUE, dwNotifyFilter, NULL, &g_overlapped, DirectoryChangeCallback)) { if (GetLastError() != ERROR_IO_PENDING) { wprintf(L"启动监控失败,错误码: %d\n", GetLastError()); CloseHandle(g_hDirectory); return 1; } } wprintf(L"开始监控C:\\Temp目录...\n"); // 使用可警告等待,让线程处理APC回调 while (true) { DWORD dwWaitResult = WaitForSingleObjectEx(g_hDirectory, INFINITE, TRUE); if (dwWaitResult == WAIT_OBJECT_0 || dwWaitResult == WAIT_IO_COMPLETION) { // 回调已处理事件,继续等待下一次变化 continue; } else { wprintf(L"等待出错,错误码: %d\n", GetLastError()); break; } } // 清理资源 CloseHandle(g_hDirectory); return 0; }
额外说明
WaitForSingleObjectEx的第三个参数设为TRUE后,线程在等待期间会处理APC队列中的回调函数,这是触发DirectoryChangeCallback的核心前提。- 回调函数中必须重新调用
ReadDirectoryChangesW,否则只能捕获一次目录变化,之后会停止监控。 - 代码中添加了通知信息解析逻辑,可直观看到目录变化的类型和对应的文件名。
内容的提问来源于stack exchange,提问作者KungPhoo
相关产品推荐
相关产品推荐

