The documentation 指出常见的文件系统操作可能引发多个事件。检查事件和缓冲区大小标题下。
常见的文件系统操作可能引发多个事件。例如,当一个文件从一个目录移动到另一个目录时,可能会引发几个 OnChanged 以及一些 OnCreated 和 OnDeleted 事件。移动文件是一项复杂的操作,由多个简单的操作组成,因此会引发多个事件。同样,某些应用程序(例如,防病毒软件)可能会导致 FileSystemWatcher 检测到的其他文件系统事件。
它还提供了一些指南,包括:
让您的事件处理代码尽可能短。
为此,您可以使用FileSystemWatcher.Changed 事件将文件排队等待处理,然后再处理它们。这是一个使用System.Threading.Timer 实例处理队列的简单示例。
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
public class ServiceClass
{
public ServiceClass()
{
_processing = false;
_fileQueue = new ConcurrentQueue<string>();
_timer = new System.Threading.Timer(ProcessQueue);
// Schedule the time to run in 5 seconds, then again every 5 seconds.
_timer.Change(5000, 5000);
}
private void objWatcher_OnChanged(object sender, FileSystemEventArgs e)
{
// Just queue the file to be processed later. If the same file is added multiple
// times, we'll skip the duplicates when processing the files.
_fileQueue.Enqueue(e.FilePath);
}
private void ProcessQueue(object state)
{
if (_processing)
{
return;
}
_processing = true;
var failures = new HashSet<string>();
try
{
while (_fileQueue.TryDequeue(out string fileToProcess))
{
if (!File.Exists(fileToProcess))
{
// Probably a file that was added multiple times and it was
// already processed.
continue;
}
var file = new FileInfo(fileToProcess);
if (FileIsLocked(file))
{
// File is locked. Maybe you got the Changed event, but the file
// wasn't done being written.
failures.Add(fileToProcess);
continue;
}
try
{
fileInfo.MoveTo(/*Your destination*/);
}
catch (Exception)
{
// File failed to move. Add it to the failures so it can be tried
// again.
failutes.Add(fileToProcess);
}
}
}
finally
{
// Add any failures back to the queue to try again.
foreach (var failedFile in failures)
{
_fileQueue.Enqueue(failedFile);
}
_processing = false;
}
}
private bool IsFileLocked(FileInfo file)
{
try
{
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read,
FileShare.None))
{
stream.Close();
}
}
catch (IOException)
{
return true;
}
return false;
}
private System.Threading.Timer _timer;
private bool _processing;
private ConcurrentQueue<string> _fileQueue;
}
归功于它应得的,我从this answer那里拿走了FileIsLocked。
您可能需要考虑的其他一些事项:
如果您的FileSystemWatcher 错过了活动会怎样? [文档]确实声明这是可能的。
请注意,当超出缓冲区大小时,FileSystemWatcher 可能会错过事件。为避免错过活动,请遵循以下准则:
通过设置 InternalBufferSize 属性增加缓冲区大小。
避免使用长文件名查看文件,因为长文件名会导致缓冲区被填满。考虑使用较短的名称重命名这些文件。
让您的事件处理代码尽可能短。
如果您的服务崩溃,但写入备份文件的进程继续写入它们会怎样?当您重新启动服务时,它会拾取这些文件并移动它们吗?