FileSystemWatcher 多次触发事件的强大解决方案

A robust solution for FileSystemWatcher firing events multiple times

FileSystemWatcher 事件可以触发多次。如果我的代码需要可预测的行为,那就不好了。

这在MSDN documentation中有描述:

Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.

在特定事件中很好地使用 NotifyFilters 有所帮助,但不会让我对一致性有 100% 的信心。

这是一个示例,重新创建一个记事本写入示例(但我在其他写入操作中也遇到过这种情况):

 public ExampleAttributesChangedFiringTwice(string demoFolderPath)
 {
     var watcher = new FileSystemWatcher()
     {
          Path = @"c:\temp",
          NotifyFilter = NotifyFilters.LastWrite,
          Filter = "*.txt"
      };

      watcher.Changed += OnChanged;
      watcher.EnableRaisingEvents = true;
  }

  private static void OnChanged(object source, FileSystemEventArgs e)
  {
      // This will fire twice if I edit a file in Notepad
  }

有什么建议可以让它更有弹性吗?

编辑:意思是在触发多个事件时不重复多个动作。

一种利用 MemoryCache 作为缓冲区的方法,将 'throttle' 附加事件。

  1. 一个文件事件(本例中的Changed)被触发
  2. 事件由 OnChanged 处理,但它没有完成所需的操作,而是将事件存储在 MemoryCache 中 1秒过期 和一个 CacheItemPolicy 回调设置以在到期时执行。

请注意,我使用 AddOrGetExisting 作为一种简单的方法来阻止在缓存期间触发的任何其他事件被添加到缓存中。

  1. 当它过期时,回调 OnRemovedFromCache 完成针对该文件事件的行为

.

  class BlockAndDelayExample
{
    private readonly MemoryCache _memCache;
    private readonly CacheItemPolicy _cacheItemPolicy;
    private const int CacheTimeMilliseconds = 1000;

    public BlockAndDelayExample(string demoFolderPath)
    {
        _memCache = MemoryCache.Default;

        var watcher = new FileSystemWatcher()
        {
            Path = demoFolderPath,
            NotifyFilter = NotifyFilters.LastWrite,
            Filter = "*.txt"
        };

        _cacheItemPolicy = new CacheItemPolicy()
        {
            RemovedCallback = OnRemovedFromCache
        };

        watcher.Changed += OnChanged;
        watcher.EnableRaisingEvents = true;
    }

    // Add file event to cache for CacheTimeMilliseconds
    private void OnChanged(object source, FileSystemEventArgs e)
    {
        _cacheItemPolicy.AbsoluteExpiration =
            DateTimeOffset.Now.AddMilliseconds(CacheTimeMilliseconds);

        // Only add if it is not there already (swallow others)
        _memCache.AddOrGetExisting(e.Name, e, _cacheItemPolicy);
    }

    // Handle cache item expiring
    private void OnRemovedFromCache(CacheEntryRemovedArguments args)
    {
        if (args.RemovedReason != CacheEntryRemovedReason.Expired) return;

        // Now actually handle file event
        var e = (FileSystemEventArgs) args.CacheItem.Value;
    }
}

可以轻松扩展到:

  • 检查缓存中过期的文件锁,如果不可用,将其再次放回缓存中(有时事件触发得太快,文件还没有准备好进行某些操作)。最好使用 try/catch 循环。
  • 文件名 + 事件类型组合的密钥缓存

我使用 FileSystemWatcher 检查正在上传的 MP4 文件,我最终必须对其进行处理。上传过程似乎没有对文件建立任何锁定,所以我以前为过早开始处理它们而苦苦挣扎。

我最终采用的技术(对我的案例而言完全成功)是使用事件并将文件路径添加到 Dictionary<string, long> 可能感兴趣的文件,然后启动计时器。我定期(60 秒)检查文件大小。 long 字典值保存上次检查的文件大小,如果当前大小更大,我认为它仍在写入,存储新大小并再返回休眠 60 秒。

在 60 秒内没有写入 activity 的情况下,我可以开始处理。

如果这不适合您,您还可以考虑其他一些事项;每分钟哈希一次文件并存储哈希,定期重新哈希直到内容没有改变。密切关注文件系统中的上次修改日期,也许

最终,请考虑 FileSYstemWatcher 可能是一个有用的设备,它不会通知您必须对哪些文件采取行动,而是通知您可能感兴趣的文件,并且具有更精细的内部逻辑的单独进程可以决定是否应该对一个可能有趣的文件采取行动