文件系统监控是软件开发中一个常见的需求,尤其是在需要对文件变化做出响应的场景中。.NET框架中的FileSystemWatcher类为此提供了便利,但是它在某些情况下可能会表现出不可预测的行为,例如对单个操作触发多次事件。尽管这不是FileSystemWatcher的缺陷,但确实给开发者带来了额外的挑战。
例如,当一个文件正在被写入时,可能会首先触发一个事件表示文件写入开始,然后再次触发一个事件表示写入结束。虽然这种行为在某些情况下是可预测的,但并不能保证在所有操作系统和应用程序中都能保持一致。
考虑一个简单的场景,在Notepad中编辑一个位于c:\temp目录下的文本文件。在这个过程中,FileSystemWatcher可能会触发两次事件。如果能够确信这种行为是一致的,那么可以选择只关注第二次事件。然而,这种假设并不总是安全的。
public class ExampleAttributesChangedFiringTwice
{
public ExampleAttributesChangedFiringTwice(string demoFolderPath)
{
var watcher = new FileSystemWatcher()
{
Path = demoFolderPath,
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.txt"
};
watcher.Changed += OnChanged;
watcher.EnableRaisingEvents = true;
}
private static void OnChanged(object source, FileSystemEventArgs e)
{
// 如果在Notepad中编辑文件,这将触发两次
}
}
为了解决这个问题,可以采用一种更加稳健的解决方案。
合理使用NotifyFilters可以帮助过滤掉一些不必要的事件,但仍然存在许多场景,即使使用了NotifyFilters,额外的事件仍然会触发。为了解决这个问题,可以使用MemoryCache作为缓冲区来“节流”额外的事件。
当一个文件事件(例如文件更改)被触发时,事件处理程序OnChanged不会立即执行预期的操作,而是将事件存储在MemoryCache中,并设置1秒的过期时间。同时,设置一个CacheItemPolicy回调,在过期时执行。
使用AddOrGetExisting方法可以简单地阻止在缓存期间添加任何额外的事件。
public 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;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
_cacheItemPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddMilliseconds(CacheTimeMilliseconds);
_memCache.AddOrGetExisting(e.Name, e, _cacheItemPolicy);
}
private void OnRemovedFromCache(CacheEntryRemovedArguments args)
{
if (args.RemovedReason != CacheEntryRemovedReason.Expired) return;
var e = (FileSystemEventArgs)args.CacheItem.Value;
// 现在实际处理文件事件
}
}