一、背景

  • 我们可以使用FileSystemWatcher监控文件系统,在特定的文件被创建、修改或删除时引发事件。
  • 程序如下:
sing System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Collections;

namespace MyNamespace
{
class MyClassCS
{
public Form1() {

InitializeComponent();

// 加载监控路径
string path1 = "D:\\1"; // 监控路径1
string[] pathArray = { path1 };
for (int i = 0; i < pathArray.Count(); i++) {
FileSystemWatcher watch = new FileSystemWatcher();
watch.Path = pathArray[i];

watch.Filter = "*";
watch.Created += new FileSystemEventHandler(OnChange);
watch.Changed += new FileSystemEventHandler(OnChange);
watch.Deleted += new FileSystemEventHandler(OnChange);

watch.EnableRaisingEvents = true;

// 监控文件名与上次写入时间
watch.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite ;
}

listBox1.Items.Clear();

}

private void OnChange(object sender, FileSystemEventArgs e) {

if (e.ChangeType == WatcherChangeTypes.Changed || e.ChangeType == WatcherChangeTypes.Created) {

FileInfo fileInfo = new FileInfo(e.FullPath);

listBox1.Invoke(new MethodInvoker(delegate {
listBox1.Items.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ff") + ","
+ e.ChangeType + "," + e.FullPath
+ "," + fileInfo.LastWriteTime
+ "," + fileInfo.Length
);
}));
}

if (e.ChangeType == WatcherChangeTypes.Deleted) {

listBox1.Invoke(new MethodInvoker(delegate {
listBox1.Items.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ff") + "," + e.ChangeType + "," + e.FullPath);
}));
}


}
....
}
}

二、问题

  • 让我们启动程序,并在监控目录中放入一个测试文件
  • C#使用FileSystemWatcher引发多次Changed事件的问题与解决技巧_文件拷贝

  • 我们惊奇地发现除了触发了一次Created事件外,还触发了两次Changed事件;
  • 按照微软官方的解释:FileSystemWatcher监视的是操作系统活动,比如当文件拷贝到某一目录时,文件会进行多次缓冲写入,也就是会多次监控到NotifyFilters.LastWrite的变化,那就有可能会引发几个OnChanged以及OnCreated的事件。
  • C#使用FileSystemWatcher引发多次Changed事件的问题与解决技巧_sed_02

三、解决技巧

  • 文件拷贝完成后才进行捕获事件,确保文件能打开
  • 已经放入目录中的文件,再次进行拷贝时,我们不希望再触发事件
  • 为了保证以上两点,我们对程序进行改造
  1. 取消对Created事件的监控,只监控Changed事件
  2. 引入一个小技巧:即使触发了Changed事件,我们通过文件写读占打开的方式,判断文件是否已经复制完毕,
  3. 引入一个Hashtable,存放监控的文件列表,并显示
public partial class Form1 : Form {

// Hashtable:用于存放指定监控目录文件下新增、修改或删除的文件表,并可以去重
HashList dateTimeDictionary = new HashList();
// 显示监控列表
Thread thread ;

public Form1() {

InitializeComponent();

// 加载监控路径
string path1 = "D:\\1"; // 监控路径1
string[] pathArray = { path1};
for (int i = 0; i < pathArray.Count(); i++) {
FileSystemWatcher watch = new FileSystemWatcher();
watch.Path = pathArray[i];

watch.Filter = "*";
// 取消Created事件监控
//watch.Created += new FileSystemEventHandler(OnChange);
watch.Changed += new FileSystemEventHandler(OnChange);
watch.Deleted += new FileSystemEventHandler(OnChange);

watch.EnableRaisingEvents = true;

watch.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite ;
}
listBox1.Items.Clear();
thread = new Thread(OnShowHashtable);//创建线程
thread.Start();
}

private void OnShowHashtable() {

while (true) {
Thread.Sleep(5000);

listBox2.Invoke(new MethodInvoker(delegate {
listBox2.Items.Clear();
foreach (var key in dateTimeDictionary.Keys) {
listBox2.Items.Add(key);

}

}));

}

}


private void OnChange(object sender, FileSystemEventArgs e) {

if (e.ChangeType == WatcherChangeTypes.Changed || e.ChangeType == WatcherChangeTypes.Created) {

FileInfo fileInfo = new FileInfo(e.FullPath);

try {
// 尝试打开文件,判断文件是否上传完毕
using (var fs = File.OpenWrite(e.FullPath)) {
}
if (!dateTimeDictionary.ContainsKey(e.FullPath)) {
dateTimeDictionary.Remove(e.FullPath);
dateTimeDictionary.Add(e.FullPath, fileInfo.LastWriteTime);

listBox1.Invoke(new MethodInvoker(delegate {
listBox1.Items.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ff") + ","
+ e.ChangeType + "," + e.FullPath
+ "," + fileInfo.LastWriteTime
+ "," + fileInfo.Length
);
}));

// ToDO

}
} catch (Exception) {
// 文件无法打开,等待

}

}

if (e.ChangeType == WatcherChangeTypes.Deleted) {
dateTimeDictionary.Remove(e.FullPath);

listBox1.Invoke(new MethodInvoker(delegate {
listBox1.Items.Add(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss:ff") + "," + e.ChangeType + "," + e.FullPath);
}));
}


}
private void Form1_FormClosed(object sender, FormClosedEventArgs e) {
if (thread != null) {
thread.Abort();
}
}
}

C#使用FileSystemWatcher引发多次Changed事件的问题与解决技巧_c#_03