【问题标题】:FileSystemWatcher does not report changes in a locked fileFileSystemWatcher 不报告锁定文件中的更改
【发布时间】:2010-08-22 08:52:17
【问题描述】:

我正在使用这样的 FileSystemWatcher 监视文件夹:

watcher = new FileSystemWatcher(folder);
watcher.NotifyFilter = NotifyFilters.Size;
watcher.Changed += changedCallback;

当我在该文件夹中的记事本中打开一个新文件并保存它时,我会收到一条通知。如果我继续写然后保存,我会收到通知。如果我关闭文件并保存它,我会收到通知。正是我想要的。

但是,事实证明,如果我在该文件夹中创建一个文件并将其共享模式设置为 FileShare.Read,然后写入该文件,则在文件关闭之前我不会收到任何通知。另一种解决方法是打开文件(例如在记事本中),这显然会导致其状态被更新,然后我的监控应用程序会收到通知。另一个解决方法是我可以在 Windows 资源管理器中进行刷新,这再次导致文件状态被更新。

有趣的是,如果我在进行更改时查看 Windows 资源管理器,我会注意到:

  1. 如果文件被共享以供读写,一旦我保存它,它的大小将立即在 Windows 资源管理器中更新。
  2. 如果文件是只读共享的,它的大小不会在 Windows 资源管理器中立即更新,除非我手动刷新窗口。

因此,我的监控应用程序似乎与 Windows 资源管理器具有相同的行为。我正在考虑运行一个线程来扫描文件夹中的文件,但我想知道在这种情况下是否有更优雅的事情要做。

顺便说一句,我使用的是 Win7,我不确定其他 Windows 版本是否也会出现此问题。

谢谢!

编辑:在 C++ 中使用 ReadDirectoryChanges 得到了完全相同的结果。实现我之前谈到的线程也没有帮助。我想知道 Windows 资源管理器中的 F5 实际上在做什么,因为它确实会导致报告更改。

【问题讨论】:

    标签: c# filesystemwatcher


    【解决方案1】:

    解决问题的方法不是打开文件,而是实际读取文件。甚至读取一个字节就足够了,Windows缓存机制会将文件的内容写入磁盘,从而允许您读取它们。

    我最终实现了一个遍历所有文件的线程,打开它们并从中读取一个字节。这导致它们发生变化,并触发了 FileSystemWatcher 对象中的事件。

    Windows Explorer F5 也能正常工作的原因是 Windows 实际上会读取文件的内容以显示一些扩展内容(例如缩略图)。一旦文件被读取,缓存首先写入磁盘,触发FSW中的事件。

    【讨论】:

      【解决方案2】:

      是的,Explorer 使用与 FileSystemWatcher 完全相同的 API。你发现只有一个,ReadDirectoryChangesW()。

      您的发现强烈表明 Win7 正在优化更新文件目录条目所需的磁盘写入。将其延迟到文件关闭的最后一刻。这一观察结果与用户在 RTM 版本的 Win7 中发现的一个严重错误之间存在一个有趣的关联。更新有时根本不会发生。该错误随机但很少发生,我自己在我的机器上只见过一次。反正明知道。

      详细信息在this thread 中(注意非常慢的服务器)。在应用了所有 Win7 更新后,今天仍然失败。

      嗯,有趣的花絮,但与您的问题并不密切相关。您确实需要更改代码以适应操作系统的工作。

      【讨论】:

      • 感谢您的回复,汉斯。不过,鉴于我无法更改写作申请,您对我应该如何进行有什么建议吗?我想知道如何模仿 Explorer 的 F5(它确实会更新文件大小并导致我的应用程序触发事件)。我尝试获取文件列表,甚至一一打开,但没有帮助。
      • 嗯,有趣的是,Explorer 可以让您的 FSW 事件触发。我只能想到 FlushFileBuffers() 但肯定不是这样。也许您可以在 SysInternals 的 ProcMon 堆栈跟踪中看到它。
      • 原来是资源管理器读取文件,导致(这是我自己的理论)缓存机制将写入刷新到磁盘,以便阅读器实际上可以获得最新的内容。有关详细信息,请参阅我对问题的回答。另外,感谢您的 cmets!
      【解决方案3】:

      我在编写 Windows 服务时遇到了同样的 FileSystemWatcher 问题。该服务是用 .NET 编写的,并使用 FileSystemWatcher 来监控第三方产品生成的日志文件。在测试期间,我在这个第三方产品中执行了一些我知道强制写入日志条目的操作,但在我在记事本中打开目标日志文件或在 Windows 资源管理器中刷新视图之前,我的服务的断点从未触发。

      我的解决方案是在创建 FileSystemWatcher 的同时创建一个 FileInfo 实例(我们将称之为 fileInfoInstance)。每当我启动或停止我的 FileSystemWatcher 时,我也会启动或停止一个 System.Threading.Timer,它的回调每 N 毫秒调用一次 fileInfoInstance.Refresh()。似乎 fileInfoInstance.Refresh() 会刷新缓冲区/写入缓存,并允许 FileSystemWatcher 事件以与在资源管理器中按 F5 相同的方式引发。

      有趣(遗憾的是)足够多的 fileInfoInstance.Directory.Refresh() 并没有达到相同的结果,所以如果您正在观看多个文件,即使它们都在同一个目录中并被同一个观察者观看,您需要为您正在观看的每个文件提供一个 FileInfo 实例,并且您的计时器回调应该用每个“tick”刷新它们......

      编码愉快。

      布赖恩

      【讨论】:

      • 最终我通过打开所有文件进行阅读解决了这个问题。在这种情况下为每个文件保存一个 FileInfo 实例不是很合适,因为在目录中添加和删除文件,并且可能有数百个。在我的情况下,按顺序阅读它们会更好,但我可以看到你的情况有何不同。
      【解决方案4】:

      看起来文件数据被缓存而不是实际写入。当您向文件写入内容时,首先使用某些文件系统 IRP(驱动程序请求)将数据放置到缓存中。现在,当数据实际写入磁盘时,会发送另一个 IRP。 FileSystemWatcher 可能只捕获第二种 IRP 类型(这是一个猜测)。

      解决方案(如果可能)是在您正在编写的文件上调用 Flush。当然,如果您正在跟踪其他应用程序所做的更改,事情可能会变得更加复杂。

      【讨论】:

      • 谢谢。不幸的是,我没有控制写作应用程序。
      • 我认为知道资源管理器中的 F5 正在做什么也无济于事,因为 F5 是一个主动操作,您不能仅仅因为您不知道文件何时更改而采取此类操作.
      • 我正在考虑在我自己的线程中模拟刷新。我的另一个选择是在我自己的线程中重写 FileSystemWatcher 来检查文件 - 但如果数据没有实际写入磁盘,即使这样也可能不起作用。刷新 Windows 资源管理器似乎会将缓冲区刷新到磁盘,这就是为什么我想了解它是如何工作的。
      • 也许这个讨论会有所帮助:stackoverflow.com/questions/173560/flush-disk-write-cache
      • 尤金,感谢您的帮助。您的见解使我走上了正轨。详情可以看我的回答。
      【解决方案5】:

      您确实需要一个线程来打开所有文件并从中读取一个字节,我的程序在 Windows 7 而不是 XP 上运行后停止工作,我使用了以下代码

      private void SingleByteReadThread(object notUsed)
          {
             while (true)
            {
               foreach (FileInfo fi in new DirectoryEnumerator(folderPath))
                        {
                            using (FileStream fs = new FileStream(fi.FullName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                                fs.ReadByte();    
                        }
      
                Thread.Sleep(TimeSpan.FromSeconds(2));
            }
        }
      

      DirectoryEnumerator 是我自己的类

      【讨论】:

      • 只是为了记录,这可以不使用 Directory.EnumerateFiles 而不是一些自定义的 DirectoryEnumerator 以及完美的工作!
      【解决方案6】:

      您必须调用 FileStream Flush() 方法来写入文件更改。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-22
        相关资源
        最近更新 更多