在开发应用程序时,经常需要监控文件夹以检测新文件的到来,并将这些文件发送到其他地方,甚至可能将其包装成Windows服务。在调试过程中,一切可能都运行得很好。但是,当将其移至测试环境并让手动测试人员开始测试时,他们复制一个文件,文件监控器立即检测到新文件并执行操作,然后突然发生了IOException:因为另一个进程正在使用文件X,导致无法访问。这通常是因为复制操作尚未完成,而事件就已经触发了。即使文件不大,监控器也可能过于高效。
当文件系统事件发生时,将其详细信息存储在内存缓存中一段时间。设置一个回调,当事件从内存缓存中过期时执行。在回调中,检查文件是否可用于写操作。如果是,则继续执行预定的文件操作。如果不是,则将其放回内存缓存中一段时间,并重复上述步骤。跟踪并限制获取文件锁定的重试次数是有意义的。
基于之前关于处理多个FileSystemWatcher事件的帖子中的代码构建了这个解决方案。这个示例解决方案的完整代码可以在这里找到。
当处理文件系统事件时,使用一个简单的POCO将文件详细信息和重试计数存储在MemoryCache中,并设置一个计时器,例如60秒:
private void OnCreated(object source, FileSystemEventArgs e)
{
_cacheItemPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CacheTimeSeconds);
var fileData = new CacheItemValue()
{
FilePath = e.FullPath,
RetryCount = 0,
FileName = e.Name
};
_memCache.AddOrGetExisting(e.Name, fileData, _cacheItemPolicy);
}
一个简单的POCO:
class CacheItemValue
{
public string FileName { get; set; }
public string FilePath { get; set; }
public int RetryCount { get; set; }
}
在构造函数中,初始化了缓存项策略,当这些缓存的POCOs过期时执行回调:
_cacheItemPolicy = new CacheItemPolicy
{
RemovedCallback = OnRemovedFromCache
};
private void OnRemovedFromCache(CacheEntryRemovedArguments args)
{
// Checking if expired, for a bit of future-proofing
if (args.RemovedReason != CacheEntryRemovedReason.Expired)
return;
var cacheItemValue = (CacheItemValue)args.CacheItem.Value;
if (cacheItemValue.RetryCount > MaxRetries)
return;
if (IsFileLocked(cacheItemValue.FilePath))
{
cacheItemValue.RetryCount++;
_cacheItemPolicy.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(CacheTimeSeconds);
_memCache.Add(cacheItemValue.FileName, cacheItemValue, _cacheItemPolicy);
}
// Now safe to perform the file operation here...
}