16

我正在使用这样的 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 Explorer 中的 F5 实际上在做什么,因为它确实会导致报告更改。

4

6 回答 6

12

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

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

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

于 2010-08-29T12:06:03.923 回答
4

是的,Explorer 使用与 FileSystemWatcher 完全相同的 API。如您所见,只有一个 ReadDirectoryChangesW()。

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

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

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

于 2010-08-22T15:23:08.310 回答
4

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

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

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

快乐编码。

布赖恩

于 2010-11-10T15:35:21.490 回答
2

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

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

于 2010-08-22T09:56:31.773 回答
1

您确实需要一个线程来打开所有文件并从中读取一个字节,我的程序在 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 是我自己的类

于 2010-11-21T00:31:19.290 回答
0

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

于 2013-12-17T10:25:48.507 回答