我需要设置一个应用程序来监视在本地或网络驱动器上的目录中创建的文件。
FileSystemWatcher
或对计时器进行轮询将是最佳选择。我过去使用过这两种方法,但没有广泛使用。
这两种方法都有哪些问题(性能、可靠性等)?
我已经看到文件系统观察程序在生产和测试环境中失败。我现在认为它很方便,但我认为它不可靠。我的模式一直是使用文件系统观察程序来观察更改,但偶尔轮询以捕获丢失的文件更改。
编辑:如果您有 UI,您还可以让您的用户能够“刷新”更改而不是轮询。我会将它与文件系统观察程序结合起来。
我遇到的最大问题是缓冲区满时文件丢失。像馅饼一样容易修复 - 只需增加缓冲区。请记住,它包含文件名和事件,因此将其增加到预期的文件数量(试错)。它确实使用无法分页的内存,因此如果内存不足,它可能会强制其他进程进行分页。
这是有关缓冲区的 MSDN 文章:FileSystemWatcher..::.InternalBufferSize Property
根据 MSDN:
增加缓冲区大小是昂贵的,因为它来自无法换出到磁盘的非分页内存,因此请保持缓冲区尽可能小。为避免缓冲区溢出,请使用 NotifyFilter 和 IncludeSubdirectories 属性过滤掉不需要的更改通知。
我们使用 16MB,因为一次预计会有大批量。工作正常,从不错过文件。
我们还在开始处理甚至一个文件之前读取了所有文件……将文件名安全地缓存起来(在我们的例子中,缓存到数据库表中)然后处理它们。
对于文件锁定问题,我生成了一个进程,该进程等待文件被解锁等待一秒钟,然后是两秒钟,然后是四秒钟,等等。我们从不投票。这已经在生产中没有错误大约两年。
如果排队更改的数量超出提供的缓冲区,FileSystemWatcher
也可能在繁忙时间错过更改。这不是 .NET 类本身的限制,而是底层 Win32 基础结构的限制。根据我们的经验,最小化此问题的最佳方法是尽快将通知出列并在另一个线程上处理它们。
正如上面@ChillTemp 所提到的,观察者可能无法在非 Windows 共享上工作。例如,它在安装的 Novell 驱动器上根本不起作用。
我同意一个很好的折衷办法是不定期地进行一次民意调查以找出任何错过的更改。
另请注意,文件系统观察程序在文件共享上不可靠。特别是如果文件共享托管在非 Windows 服务器上。 FSW 不应该用于任何关键的事情。或者应该与偶尔的民意调查一起使用,以验证它没有遗漏任何东西。
就我个人而言,我在生产系统上使用了 FileSystemWatcher
,它运行良好。在过去的 6 个月中,它没有出现过 24x7 全天候运行的问题。它正在监视单个本地文件夹(共享)。我们必须处理的文件操作数量相对较少(每天触发 10 个事件)。这不是我曾经担心过的事情。如果我不得不重新做出决定,我会再次使用它。
我目前在平均每 100 毫秒更新一次的 XML 文件上使用 FileSystemWatcher
。
我发现只要正确配置了 FileSystemWatcher
,您就不会遇到 local 文件的问题。
我没有远程文件观看和非 Windows 共享的经验。
我会认为轮询文件是多余的,不值得开销,除非您天生不信任 FileSystemWatcher
或直接经历了此处其他所有人列出的限制(非 Windows 共享和远程文件监视)。
我在网络共享上使用 FileSystemWatcher
时遇到了麻烦。如果您在纯 Windows 环境中,这可能不是问题,但我正在观看 NFS 共享,并且由于 NFS 是无状态的,因此当我正在观看的文件发生更改时,从来没有通知。
我会去投票。
网络问题导致 FileSystemWatcher
不可靠(即使在超载错误事件时也是如此)。
我在网络驱动器上使用 FSW 时遇到了一些大问题:删除文件总是抛出错误事件,而不是删除事件。我没有找到解决方案,所以我现在避免使用 FSW 并使用轮询。
另一方面,创建事件运行良好,因此如果您只需要注意文件创建,则可以使用 FSW。
此外,无论是否共享,我在本地文件夹上都没有问题。
尽快从事件方法返回,使用另一个线程,为我解决了这个问题:
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
Task.Run(() => MySubmit(e.FullPath));
}
在我看来,同时使用 FSW 和轮询是浪费时间和资源,我很惊讶有经验的开发人员建议这样做。如果您需要使用轮询来检查任何“FSW 未命中”,那么您自然可以完全丢弃 FSW 并仅使用轮询。
目前,我正在尝试决定是否将使用 FSW 或轮询来开发我开发的项目。阅读答案,很明显在某些情况下 FSW 可以完美地满足需求,而其他时候则需要轮询。不幸的是,没有答案实际上处理了性能差异(如果有的话),只有“可靠性”问题。有没有人可以回答这部分问题?
编辑:nmclean 关于同时使用 FSW 和轮询的有效性的观点(如果您有兴趣,可以阅读评论中的讨论)似乎是一个非常合理的解释,为什么在某些情况下同时使用 FSW 和轮询是有效的。感谢您为我(以及其他任何有相同观点的人)阐明这一点,nmclean。
使用创建事件而不是更改的工作解决方案
即使是复制、剪切、粘贴、移动。
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;
}
}
这是针对多触发事件这一问题的变通解决方案。
我会说使用轮询,尤其是在 TDD 场景中,因为在触发轮询事件时模拟/存根文件的存在或其他情况比依赖更“不受控制”的 fsw 事件要容易得多。 + 在许多受 fsw 错误困扰的应用程序上工作过。
不定期副业成功案例分享