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

如何避免遍历目录时ReadDirectoryChangesW返回虚假Modified事件

使用ReadDirectoryChangesW检测目录树时遍历刚创建的目录触发虚假Modified事件

在使用ReadDirectoryChangesW()检测目录树变更的过程中,发现一个问题:遍历刚创建的目录树时,该函数会返回虚假的Modified事件。

操作步骤

  • 创建目录树
  • 调用ReadDirectoryChangesW()监控该目录树
  • 遍历目录树

结果对比

预期结果:ReadDirectoryChangesW()不会报告任何变更
实际结果:ReadDirectoryChangesW()报告Modified类型的变更事件

问题复现代码

#include <windows.h>
#include <filesystem>
#include <iostream>
#include <string>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>

namespace
{
    HANDLE createHandle(std::filesystem::path const& directory) {
        std::wstring path = directory.wstring();
        LPCWSTR dirPath = static_cast<LPCWSTR>(path.c_str());
        HANDLE handle = CreateFileW(
            dirPath,
            FILE_LIST_DIRECTORY,
            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
            NULL);
        return handle;
    }

    class DirectoryWatcher
    {
    private:
        std::filesystem::path _rootDir;

        HANDLE _dirHandle;
        DWORD _bufferSizeInBytes;
        std::unique_ptr<DWORD> _buffer;
        OVERLAPPED _overlapped;

        std::mutex _mutex;
        std::condition_variable _cond;
        bool _running;
        std::atomic<bool> _stop;
        std::unique_ptr<std::thread> _watcher;

    public:
        DirectoryWatcher(std::filesystem::path const& directory)
            : _rootDir(directory)
            , _dirHandle(createHandle(_rootDir))
            , _bufferSizeInBytes(32 * 1024)
            , _buffer(new DWORD[_bufferSizeInBytes / sizeof(DWORD)])
            , _running(false)
            , _stop(false)
        {
            if (_dirHandle == INVALID_HANDLE_VALUE) throw std::runtime_error("dir handle creation failed");
            memset(&_overlapped, 0, sizeof(OVERLAPPED));
            _overlapped.hEvent = CreateEvent(NULL, FALSE, 0, NULL);
            if (_overlapped.hEvent == INVALID_HANDLE_VALUE) throw std::runtime_error("event handle creation failed");
            _watcher = std::make_unique<std::thread>(&DirectoryWatcher::run, this);

            std::unique_lock<std::mutex> lock(_mutex);
            while (!_running) _cond.wait(lock);
        }

        ~DirectoryWatcher() {
            _stop = true;
            SetEvent(_overlapped.hEvent); // 唤醒WaitForSingleObject
            _watcher->join();
            CancelIo(_dirHandle);
            CloseHandle(_overlapped.hEvent);
            CloseHandle(_dirHandle);
        }

    private:
        void queueReadChangeRequest() {
            BOOL success = ReadDirectoryChangesW(
                _dirHandle, _buffer.get(), _bufferSizeInBytes, TRUE,
                FILE_NOTIFY_CHANGE_FILE_NAME |
                FILE_NOTIFY_CHANGE_DIR_NAME |
                FILE_NOTIFY_CHANGE_LAST_WRITE,
                NULL, &_overlapped, NULL);
            if (!success) throw std::runtime_error("ReadDirectoryChangesW failed");
        }

        void run() {
            const DWORD waitTimeoutInMs = 100;
            queueReadChangeRequest();
            {
                std::lock_guard<std::mutex> lock(_mutex);
                _running = true;
                _cond.notify_one();
            }
            while (!_stop) {
                DWORD result = WaitForSingleObject(_overlapped.hEvent, INFINITE);
                if (!_stop && result == WAIT_OBJECT_0) {
                    DWORD bytesTransferred;
                    GetOverlappedResult(_dirHandle, &_overlapped, &bytesTransferred, FALSE);
                    if (bytesTransferred == 0) {
                        std::cout << "Overflow" << std::endl;
                    } else {
                        std::size_t offset = 0;
                        PFILE_NOTIFY_INFORMATION info;
                        do
                        {
                            info = reinterpret_cast<PFILE_NOTIFY_INFORMATION>(&_buffer.get()[offset]);
                            offset += info->NextEntryOffset;
                            std::wstring fileNameStr(info->FileName, info->FileNameLength / sizeof(wchar_t));
                            std::filesystem::path fileName(_rootDir / fileNameStr);
                            if (info->Action == FILE_ACTION_ADDED) {
                                std::cout << "Added " << fileName << std::endl;
                            } else if (info->Action == FILE_ACTION_REMOVED) {
                                std::cout << "Removed " << fileName << std::endl;
                            } else if (info->Action == FILE_ACTION_MODIFIED) {
                                std::cout << "Modified " << fileName << std::endl;
                            } else if (info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
                                std::cout << "Renamed old name " << fileName << std::endl;
                            } else if (info->Action == FILE_ACTION_RENAMED_NEW_NAME) {
                                std::cout << "Renamed new name " << fileName << std::endl;
                            }
                        } while (info->NextEntryOffset != 0);
                    }
                    queueReadChangeRequest();
                }
            }
        }
    };

    void iterateDirectory(std::filesystem::path const& dir)
    {
        for (auto const& entry : std::filesystem::directory_iterator(dir)) {
            if (entry.is_directory()) {
                std::filesystem::path s3(dir / entry.path());
                for (auto const& subentry : std::filesystem::directory_iterator(dir)) {
                }
            }
        }
    }
}

int main() {
    std::string tmpDir(std::tmpnam(nullptr));
    std::filesystem::path rootDir(std::string(tmpDir + "_spuriousEvents"));
    std::filesystem::path subDir0(rootDir / "subDir0");
    std::filesystem::path subDir00(subDir0 / "subDir00");
    std::filesystem::create_directory(rootDir);
    std::filesystem::create_directory(subDir0);
    std::filesystem::create_directory(subDir00);
    std::cout << "Created directory " << rootDir.string() << std::endl;
    std::cout << "Created directory " << subDir0.string() << std::endl;
    std::cout << "Created directory " << subDir00.string() << std::endl;

    DirectoryWatcher watcher(rootDir);
    std::cout << std::endl << "Started watching directory tree " << rootDir.string() << std::endl;

    std::cout << std::endl << "Unexpected event during first directory iteration" << std::endl;
    iterateDirectory(subDir0);

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << std::endl << std::endl;
    std::cout << "No unexpected event during subsequent directory iterations" << std::endl;
    iterateDirectory(subDir0);
    iterateDirectory(subDir0);

    char input = '\0';
    while (input == '\0') std::cin >> input;
}

内容的提问来源于stack exchange,提问作者Peter Jaspers

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 03:10:14