一个更好的文件监控类,基于 DotNet 官方提供的 FileSystemWatcher
缘起
前一段时间,想使用
.net
监听特定文件夹中的文件是否发生变化。网上一搜,可以使用
.net
官方提供的
FileSystemWatcher
,很开心的写好了代码。随着使用的不断深入,发现了
FileSystemWatcher
几个不够完善的地方。比如,
- 事件触发时,文件可能还不能被访问。
- 如果监听选项设置的过多,则有可能会触发多次文件变化事件。
-
监听过滤器不够灵活,我没找到同时监听多种特定文件类型的方法。比如,同时只监听
.docx
和.bmp
文件。
鉴于此,基于
.net
官方提供的
FileSystemWatcher
,我又封装了一个新的类。可以在一定程度上解决以上几个问题。
问题及解决方案
-
当事件触发的时候,文件还不能被访问。如何重现?
重现方法:
通过共享文件夹拷贝一个大文件,基本上可以重现。
解决方案:
在调用用户的回调函数前,先判断文件是否可以访问。
实现代码:
WaitForFileReadyToAccess
可以用来开启/关闭此功能。
WaitUntilCanAccess()
不断尝试访问文件,以此来确定是否可以访问该文件。
用户可以通过FileAccessCheckIntervalMs
来设置尝试间隔,单位是毫秒。
用户可以通过MaxWaitMs
设置最大等待时间,单位是毫秒。
-
相同的事件触发多次。如何重现?
重现方法:
当NotifyFilters
设置成下面的样子,拷贝一张文件到监听路径下即可重现。
NotifyFilters _notifyFilters = NotifyFilters.DirectoryName | NotifyFilters.FileName
| NotifyFilters.LastWrite | NotifyFilters.CreationTime | NotifyFilters.LastAccess
| NotifyFilters.Attributes | NotifyFilters.Size | NotifyFilters.Security
;
解决方案:
在调用用户的回调函数前,稍微等一段时间,合并这段时间内的相同事件。
实现代码:
TryMergeSameEvent
用来开启/关闭事件合并功能。
如果TryMergeSameEvent
为真,那么会通过eventDataList = eventDataList.Distinct().ToList();
来去重。
用户可以通过DelayTriggerMs
指定延时触发间隔,单位是毫秒。只有在合并事件开启的时候才生效。
-
不能同时监听多种特定类型。
重现方法:
我没能找到同时监听多种特定类型的方法。
解决方案:
封装一个新类。用户可以通过|
分割多个过滤条件。根据每个过滤条件构造对应的由系统提供的FileSystemWatcher
,保存到
List<FileSystemWatcher>
中。
实现代码:
char [] splitter = { '|' };
var filterList = Filters.Split(splitter).ToList();
foreach ( var filter in filterList)
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Filter = filter;
watcher.Path = this .Path;
watcher.IncludeSubdirectories = this .Recursive;
watcher.NotifyFilter = this .NotifyFilters;
watcher.Created += this .OnFileSystemEventHandler;
watcher.Deleted += this .OnFileSystemEventHandler;
watcher.Changed += this .OnFileSystemEventHandler;
watcher.Renamed += this .OnRenamedEventHandler;
watcher.Error += this .OnErrorHandler;
watcher.EnableRaisingEvents = true;
WatcherList.Add(watcher);
}
使用示例
public void OnFileChanged(object sender, System.IO.FileSystemEventArgs e)
if (e.ChangeType == System.IO.WatcherChangeTypes.Created
|| e.ChangeType == System.IO.WatcherChangeTypes.Changed)
this.Invoke(new MethodInvoker(delegate()
using (var imageStream = new FileStream(e.FullPath, FileMode.Open))
this.pictureBox1.Image = (Bitmap)Image.FromStream(imageStream);
catch (Exception ex)
System.Diagnostics.Debug.WriteLine(string.Format("!!!! {0}", ex.ToString()));
private void Form1_Load(object sender, EventArgs e)