FileSystemWatcher无法报告锁定文件中的更改

17

我正在使用类似以下方式使用 FileSystemWatcher 监控文件夹:

watcher = new FileSystemWatcher(folder);
watcher.NotifyFilter = NotifyFilters.Size;
watcher.Changed += changedCallback;
打开同一文件夹中的Notepad新文件并保存时,我会收到通知。 如果我继续编写然后保存,我会收到通知。 如果我关闭文件而不保存,则会收到通知。 这正是我想要的。
但是,如果我在该文件夹中创建一个文件,并将其共享模式设置为FileShare.Read,然后再对其进行写入,直到关闭该文件之前,我将不会收到任何通知。 另一个解决方法是打开该文件(例如在记事本中),这显然会导致其状态更新,然后我的监控应用程序会收到通知。 另一个解决方法是在Windows资源管理器中可以刷新的操作,它也会导致文件状态更新。
有趣的是,当我查看Windows资源管理器时做出更改时,我注意到:
1. 如果文件被共享以供读取和写入,则只要我保存文件,文件大小就会立即在Windows资源管理器中更新。 2. 如果文件仅供读取共享,则除非我手动刷新窗口,否则其大小将不会立即在Windows资源管理器中更新。
因此,似乎我的监视应用程序与Windows资源管理器具有相同的行为。 我正在考虑运行一个线程,只需扫描文件夹中的文件,但我想知道在这种情况下是否有更优雅的解决方法。
顺便说一句,我正在使用Win7,不确定其他Windows版本是否也有此问题。
谢谢!
6个回答

13

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

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

Windows Explorer F5之所以有效,原因是Windows实际上读取文件的内容以显示一些扩展内容(例如缩略图)。 一旦正在读取文件,缓存首先写入磁盘,在FSW中触发事件。


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

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

有趣的是(令人遗憾的是),fileInfoInstance.Directory.Refresh()没有达到相同的结果,因此,如果您正在监视多个文件,即使它们都在同一个目录中并由同一个监视程序监视,您也需要为您正在监视的每个文件创建一个FileInfo实例,并且您的计时器回调应该在每次“tick”时刷新它们...

愉快的编码。

Brian


最终,我通过打开所有文件进行读取来解决了这个问题。在这种情况下,每个文件都持有一个FileInfo实例并不是非常适合的,因为目录中会添加和删除文件,并且可能有几百个文件。在我的情况下,按顺序阅读它们更好,但我可以看出你的情况有所不同。 - Eldad Mor

4
是的,Explorer使用与FileSystemWatcher相同的API。只有一个,就是ReadDirectoryChangesW(),正如您所发现的那样。
您发现的强烈表明Win7正在优化磁盘写入,以更新文件的目录条目。直到最后一刻才延迟它,即文件关闭时。这种观察与用户在Win7 RTM版本中发现的关键错误之间存在有趣的相关性。更新有时根本不会发生。这个错误随机但不频繁地发生,我自己的机器上也见过一次。无论如何都知道。
详情请参见this thread(注意非常慢的服务器)。即使应用了所有Win7更新,今天仍然会失败。
嗯,有趣的细节,但与您的问题并不真正相关。您需要改变代码以适应操作系统的工作方式。

谢谢回复,汉斯。但是,考虑到我无法更改写作应用程序,您有任何建议应该如何继续吗?我想知道如何模拟资源管理器的F5(它会更新文件大小并导致我的应用程序触发事件)。我尝试获取文件列表甚至逐个打开它们,但没有帮助。 - Eldad Mor
嗯,有趣的是Explorer可以触发你的FSW事件。我只能想到FlushFileBuffers(),但那肯定不是它。也许你可以在SysInternals的ProcMon堆栈跟踪中看到它。 - Hans Passant
1
原来Explorer会读取文件,这可能导致缓存机制将写入磁盘的内容刷新,以便读取器可以获取最新的内容(这是我的个人理论)。有关详细信息,请参阅我的答案。另外,感谢您的评论! - Eldad Mor

2

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

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


谢谢。不幸的是,我无法控制写作应用程序。 - Eldad Mor
我认为知道资源管理器中的F5键是做什么的也不会有太大帮助,因为F5是一种主动操作,你不能只是因为不知道文件何时被更改就采取这样的操作。 - Eugene Mayevski 'Callback
我在思考在自己的线程中模拟刷新。我的另一个选择是在自己的线程中重写FileSystemWatcher,以检查文件-但即使如此,如果数据实际上没有写入磁盘,这也可能不起作用。刷新Windows资源管理器似乎会将缓冲区刷新到磁盘,这就是为什么我想要了解它如何工作的原因。 - Eldad Mor
也许这个讨论会有所帮助:https://dev59.com/PUXRa4cB1Zd3GeqPrmsk - Eugene Mayevski 'Callback
Eugene,感谢您的帮助。您的见解让我找到了正确的方向。您可以查看我的答案以获取详细信息。 - Eldad Mor
有趣的了解,感谢您指出您的发现。 - Eugene Mayevski 'Callback

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 是我自己的类


1
只是为了记录,这可以使用Directory.EnumerateFiles而不是一些自定义的DirectoryEnumerator来完成,并且完美地工作! - CBenni

0

你需要调用FileStream Flush()方法来写入文件更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接