【问题标题】:C++ WinApi: ReadDirectoryChangesW() Receiving Double NotificationsC++ WinApi:ReadDirectoryChangesW() 接收双重通知
【发布时间】:2012-12-11 18:24:58
【问题描述】:

我尝试了解 ReadDirectoryChangesW 功能,以便我可以有效地了解多个目录中的内容更改(文件被覆盖、文件删除、重命名等)。

我最近的一个观察是,对于每个文件写入操作,我总是会收到单个文件的两个通知

我非常仔细地追踪了这一点,并且我确信,如果我覆盖了一个文件(比如一个带有新内容的 .txt 文件 - 基本上里面有几个额外的字母),ReadDirectoryChangesW() 会在每个文件保存时通知我两次.

这是一件严肃的事情,因为我希望每次更改只会收到一次通知。我不希望无意中重复在我的应用程序中应该只发生一次的操作。

这种行为已知吗?请问有没有办法每次更改只接收一个通知?有没有办法有效地避免双重通知?

我使用:

  • 非托管 C++
  • Visual Studio 2012
  • Windows 7 x64

我使用非常基本的代码来做我的测试,但你会想看看它,所以这里是:

HANDLE hDir = CreateFile(
    lpDir,
    FILE_LIST_DIRECTORY,
    FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
    NULL, 
    OPEN_EXISTING, 
    FILE_FLAG_BACKUP_SEMANTICS, 
    NULL);

    int nCounter = 0;
    FILE_NOTIFY_INFORMATION strFileNotifyInfo[1024];
    DWORD dwBytesReturned = 0;   

    while(TRUE)
    {
        if( ReadDirectoryChangesW ( hDir, (LPVOID)&strFileNotifyInfo, sizeof(strFileNotifyInfo), FALSE, FILE_NOTIFY_CHANGE_LAST_WRITE, &dwBytesReturned, NULL, NULL) == 0)
        {
            ErrorCheck(_T("Reading Directory Change"));
        }
        else
        {
            _tcout << _T("File Modified: ") << strFileNotifyInfo[0].FileName << endl;
            _tcout << _T("Loop: ") << nCounter++ << endl;
        }
    }

【问题讨论】:

  • 首先你说每个写操作,但是你的例子是覆盖。我推测从目录的角度来看,覆盖可能涉及多个操作(例如删除然后创建),而不仅仅是写入/附加到现有文件。但我对此没有任何权威参考...
  • @TheUndeadFish,嗨,谢谢你的想法。它确实非常聪明。我用另一个标志尝试了我的测试。我可以说,例如 FILE_NOTIFY_CHANGE_ATTRIBUTES 也会返回两个通知。这可以解释为,在文件覆盖时,通常会更改多个属性?

标签: c++ windows winapi visual-c++ file-io


【解决方案1】:

ReadDirectoryChangesW() 对文件系统的看法非常短视。它会看到文件系统的每一个更改,并尽职尽责地报告它们。是的,当您写入文件时,通常会有不止一个。它是您正在使用的特定文件系统的实现细节,但 Windows 中的任何常见文件系统也为存储文件元数据的文件保留目录条目。

所以你看到了文件数据的写入。但是您看到它改变了目录条目。特别是文件大小,可能会在您写入文件并向文件添加数据时发生变化。以及记录在目录条目中的最后写入和最后访问时间戳。否则,api 对所做的更改视而不见,它只看到低级写入。它也完全不知道什么特定的进程要求写入。

这是你必须处理的事情,没有办法区分这些写入。您所知道的只是“文件已更改”。完全无法发现如何、为什么、由谁以及多久发现一次。

您必须处理的其他事情是,在生成通知时,写入文件的进程很可能仍然对文件有锁定。这会阻止您自己对文件做任何有用的事情。像读取文件或复制它很可能会失败。您必须等待,直到该过程完成文件并关闭其对文件的句柄。除了尝试自己打开文件并拒绝任何共享之外,没有办法发现这一点。这需要一个计时器,定期尝试自己获取文件的锁定。设置好管道后,获得多个文件的更改通知就不再重要了。

【讨论】:

  • +1 感谢您的回答。您以非常好的方式解释了这一点,并提出了解决方案的想法。我将实施基于计时器的监控。你和 Sergmat 的答案都非常好。很难决定将哪一个标记为已接受的答案,但是,当您提供附加信息时,我正在标记您的答案。再次感谢。
【解决方案2】:

这是运行代码同时在记事本中保存文件后的 procmon 日志记录的结​​果操作。有两个来自 ReadDirectoryChangesW() 的通知和两个来自 procmon 的通知。 2 IRP_MJ_WRITE 1 来自记事本 (WriteFile) 1 来自系统缓存管理器 (CcWriteBehind)

【讨论】:

  • 这似乎是合理的,但我不确定...FILE_NOTIFY_CHANGE_LAST_WRITEmsdn.microsoft.com/en-us/library/windows/desktop/… 的文档指出“对监视目录或子树中文件的最后写入时间的任何更改都会导致更改通知等待操作返回。操作系统仅在文件写入磁盘时检测到上次写入时间的更改。对于使用广泛缓存的操作系统,仅当缓存时才会检测已充分冲洗。”
  • @sergmat:+1 晕,感谢您的解释。你的分析很棒,完美地解释了这个问题。我很抱歉,我无法自己找到答案。看起来,Sysinternals.com 的 Process Monitor 是一个非常强大的工具。
  • @NikBougalis,嗨 Nik,是的,我已从文档中阅读了该段落。看起来 Sergmat 解释了这一点,我必须根据经过的时间监控所有通知,所以我会避免那些双打。
  • 他的假设可能是正确的,但我不知道他是否解释过。他观察到的行为与文档所述相反。
  • 没什么好解释的,在截图中都可以看到。这是运行代码同时在记事本中保存文件后procmon记录的结果操作。有两个来自 ReadDirectoryChangesW() 的通知和两个来自 procmon 的通知。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多