FileSystemWatcher 与轮询以监视文件更改

我需要设置一个应用程序来监视在本地或网络驱动器上的目录中创建的文件。

FileSystemWatcher或轮询定时器将是最好的选择。我过去同时使用过这两种方法,但没有广泛使用。

这两种方法都存在哪些问题(性能、可靠性等) ?

83402 次浏览

我见过文件系统监视器在生产和测试环境中失败。我现在认为这是一种方便,但我不认为它是可靠的。我的模式一直是观察文件系统的变化,但偶尔轮询以捕捉丢失的文件变化。

编辑: 如果您有一个 UI,您也可以让您的用户能够“刷新”更改,而不是轮询。我会把它和文件系统监视器结合起来。

还要注意,文件系统监视器在文件共享上是不可靠的。特别是如果文件共享托管在非 Windows 服务器上。FSW 不应该用于任何关键。或者应该与偶尔的投票一起使用,以验证它没有遗漏任何东西。

如果队列中的更改数量溢出缓冲区,则 FileSystemWatcher也可能在繁忙时间错过更改。这不是。NET 类本身,但是基础 Win32基础结构。根据我们的经验,最小化这个问题的最佳方法是尽可能快地取消通知队列,并在另一个线程上处理它们。

正如上面@ChillTemp 提到的,观察者可能无法在非 Windows 共享上工作。例如,它根本无法在安装的 Novell 驱动器上工作。

我同意,一个好的折衷方案是偶尔进行一次民意调查,以发现任何遗漏的变化。

我在网络共享上使用 FileSystemWatcher遇到了麻烦。如果您在一个纯 Windows 环境中,这可能不是一个问题,但我正在观察一个 NFS 共享,由于 NFS 是无状态的,所以当我观察的文件更改时,从来没有通知。

我赞成民意调查。

网络问题导致 FileSystemWatcher不可靠(即使在重载错误事件时也是如此)。

就个人而言,我已经在一个生产系统上使用了 FileSystemWatcher,它工作得很好。在过去的6个月里,它没有出现过一次全天候的小问题。它监视单个本地文件夹(该文件夹是共享的)。我们有相对较少的文件操作,它必须处理(每天发射10个事件)。我从来不用担心这个。如果要我重新做决定,我还会用的。

目前,我在 XML 文件上使用 FileSystemWatcher,该文件平均每100毫秒更新一次。

我发现只要 FileSystemWatcher配置正确,就不会出现 本地文件的问题。

我没有远程文件监视和非 Windows 共享的经验。

我认为轮询文件是多余的,不值得开销,除非你本质上不信任 FileSystemWatcher或直接经历了其他人列出的限制(非 Windows 共享和远程文件监视)。

我有一些大问题与 FSW 的网络驱动器: 删除一个文件总是抛出错误事件,从来没有删除事件。我没有找到解决方案,所以我现在避免 FSW 和使用轮询。

另一方面,创建事件工作得很好,所以如果您只需要关注文件创建,那么您可以使用 FSW。

而且,我在本地文件夹上完全没有问题,无论是否共享。

我遇到的最大问题是当缓冲区满时文件丢失。很容易修复,只要增加缓冲区。请记住,它包含文件名和事件,因此将其增加到预期的文件数量(试验和错误)。它确实使用了不能被换出的内存,所以如果内存不足,它可以强制其他进程进行换页。

下面是关于 buffer 的 MSDN 文章: FileSystemWatcher. . : : . InternalBufferSize 属性

根据 MSDN:

增加缓冲区大小代价高昂,因为它来自无法交换到磁盘的非分页内存,所以要尽可能地减小缓冲区。若要避免缓冲区溢出,请使用 NotifyFilter 和 IncludeSubdirectory 属性筛选出不需要的更改通知。

我们使用16MB,因为一次需要大批量处理。工作正常,从不丢失文件。

在开始处理一个文件之前,我们也会读取所有的文件... ... 将文件名安全地缓存起来(在我们的例子中,缓存到数据库表中) ,然后处理它们。

对于文件锁定问题,我产生了一个进程,它等待文件被解锁,等待一秒,然后两秒,然后四秒,等等。我们 永远不会民意测验。这已经在生产中没有错误大约两年。

在我看来,同时使用 FSW 还有轮询是在浪费时间和资源,而且我很惊讶有经验的开发人员会这样建议。如果您需要使用轮询来检查是否有任何“ FSW 未命中”,那么您当然可以完全放弃 FSW,只使用轮询。

我,目前,试图决定是否我将使用 FSW或者轮询为我开发的项目。阅读答案,很明显,有些情况下,FSW 完全覆盖了需求,而其他时候,您 需要轮询。不幸的是,没人接实际上处理了 表演差异(如果有的话) ,只是处理了“可靠性”问题。有人能回答这部分问题吗?

编辑: nmclean 关于同时使用 FSW 和轮询的有效性的观点(如果你感兴趣,你可以阅读评论中的讨论)似乎是一个非常合理的解释,为什么会有同时使用 FSW 和轮询 有效的情况。谢谢你为我(以及其他有同样观点的人)阐明这一点,干净

我建议使用轮询,特别是在 TDD 场景中,因为当轮询事件被触发时,模拟/存根文件的存在或其他情况要比依赖更“不受控制”的 fsw 事件容易得多。+ 到已经工作了一些应用程序,这是由 fsw 错误的困扰。

用于处理创建事件而不是更改的工作解决方案

即使是复制,剪切,粘贴,移动。

class Program
{


static void Main(string[] args)
{
string SourceFolderPath = "D:\\SourcePath";
string DestinationFolderPath = "D:\\DestinationPath";
FileSystemWatcher FileSystemWatcher = new FileSystemWatcher();
FileSystemWatcher.Path = SourceFolderPath;
FileSystemWatcher.IncludeSubdirectories = false;
FileSystemWatcher.NotifyFilter = NotifyFilters.FileName;   // ON FILE NAME FILTER
FileSystemWatcher.Filter = "*.txt";
FileSystemWatcher.Created +=FileSystemWatcher_Created; // TRIGGERED ONLY FOR FILE GOT CREATED  BY COPY, CUT PASTE, MOVE
FileSystemWatcher.EnableRaisingEvents = true;


Console.Read();
}


static void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
string SourceFolderPath = "D:\\SourcePath";
string DestinationFolderPath = "D:\\DestinationPath";


try
{
// DO SOMETING LIKE MOVE, COPY, ETC
File.Copy(e.FullPath, DestinationFolderPath + @"\" + e.Name);
}
catch
{
}
}
}

使用静态存储 解决当文件属性更改事件时的文件监视器问题

class Program
{
static string IsSameFile = string.Empty;  // USE STATIC FOR TRACKING


static void Main(string[] args)
{
string SourceFolderPath = "D:\\SourcePath";
string DestinationFolderPath = "D:\\DestinationPath";
FileSystemWatcher FileSystemWatcher = new FileSystemWatcher();
FileSystemWatcher.Path = SourceFolderPath;
FileSystemWatcher.IncludeSubdirectories = false;
FileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
FileSystemWatcher.Filter = "*.txt";
FileSystemWatcher.Changed += FileSystemWatcher_Changed;
FileSystemWatcher.EnableRaisingEvents = true;


Console.Read();
}


static void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
if (e.Name == IsSameFile)  //SKIPS ON MULTIPLE TRIGGERS
{
return;
}
else
{
string SourceFolderPath = "D:\\SourcePath";
string DestinationFolderPath = "D:\\DestinationPath";


try
{
// DO SOMETING LIKE MOVE, COPY, ETC
File.Copy(e.FullPath, DestinationFolderPath + @"\" + e.Name);
}
catch
{
}
}
IsSameFile = e.Name;
}
}

这是一个解决这个问题的多重触发事件的变通方案。

使用另一个线程,尽快从事件方法返回,为我解决了这个问题:

private void Watcher_Created(object sender, FileSystemEventArgs e)
{
Task.Run(() => MySubmit(e.FullPath));
}