You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReadDirectoryChangesW回调从未触发,程序卡在WaitForSingleObject求助

问题分析与修复

你的代码卡在WaitForSingleObject且回调不触发的核心原因是:使用异步IO回调时,线程需要进入可警告等待状态才能处理APC(异步过程调用),而WaitForSingleObject不支持处理APC,导致回调永远无法被调用,事件也不会被触发。

关键问题点

  • ReadDirectoryChangesW指定回调函数后,系统会将回调作为APC放入线程队列,只有线程处于可警告等待(alertable wait)时才会执行这个APC。
  • WaitForSingleObject是不可警告的阻塞函数,会一直卡住,永远不会处理队列里的APC回调。

修复步骤

  1. 将WaitForSingleObject替换为WaitForSingleObjectEx,并将最后一个参数设为TRUE,让线程进入可警告等待状态,这样APC回调就能被执行。
  2. 在回调函数中重新调用ReadDirectoryChangesW,实现持续监控(否则只能捕获一次目录变化)。
  3. 可选优化:如果使用回调函数,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 09:07:07