我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用OnChanged事件处理程序来处理事件。我正在使用NotifyFilters。LastWriteTime,但是事件仍然被触发两次。这是代码。
public void Initialize()
{
FileSystemWatcher _fileWatcher = new FileSystemWatcher();
_fileWatcher.Path = "C:\\Folder";
_fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
_fileWatcher.Filter = "Version.txt";
_fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
_fileWatcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
.......
}
在我的情况下,OnChanged被调用两次,当我改变文本文件version.txt并保存它。
这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。
首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于2-3个更改文件的通知,而重复时间相对较长(几十秒到几分钟)。
早期建议的最有趣的链接之一是FileSystemWatcher is a Bit Broken。然而,所提出的解决方案只是部分工作,正如同一作者在。net MemoryCache Expiration Demystified的不稳定行为中指出的那样,即使在20秒后也会发出通知。
然后我所做的是基于类似的原则设计一个愚蠢的替代解决方案,没有MemoryCache。
基本上,它创建了一个List<>的项目,其中有一个Key,它是文件的完整路径和一个过期计时器。如果另一个事件再次触发该更改,则在列表中找到该元素,计时器将更新为新的过期时间。
根据经验,过期时间足够长,足以在单个OnStableChange通知中收集多个事件,而不会太长,以至于感觉没有响应。
当你实例化Whatever时,你也将它链接到一个目录和一个非常基本的外部回调。
没有什么是真正优化的,我只是在几行中寻找一个解决方案。
我把它发表在这里
对我来说,这样你就可以在另一个应用程序上验证
某个更聪明、更有经验的人可以改进它,并帮助我了解它的不足之处
internal class Whatever
{
private FileSystemWatcher? watcher = null;
public delegate void DelegateFileChange(string path);
public DelegateFileChange? onChange;
private const int CacheTimeMilliseconds = 200;
private class ChangeItem
{
public delegate void DelegateChangeItem(string key);
public string Key { get; set; } = "";
public System.Timers.Timer Expiration = new();
public DelegateChangeItem? SignalChanged = null;
}
private class ChangeCache
{
private readonly List<ChangeItem> _changes = new();
public void Set(string key, int milliSecs, ChangeItem.DelegateChangeItem? signal = null)
{
lock (_changes)
{
ChangeItem? existing = _changes.Find(item => item.Key == key);
if (existing != null)
{
existing.Expiration.Interval = milliSecs;
existing.SignalChanged = signal;
}
else
{
ChangeItem change = new()
{
Key = key,
SignalChanged = signal
};
change.Expiration.Interval = milliSecs;
change.Expiration.AutoReset = false;
change.Expiration.Elapsed += delegate { Change_Elapsed(key); };
change.Expiration.Enabled = true;
_changes.Add(change);
}
}
}
private void Change_Elapsed(string key)
{
lock (_changes)
{
ChangeItem? existing = _changes.Find(item => item.Key == key);
existing?.SignalChanged?.Invoke(key);
_changes.RemoveAll(item => item.Key == key);
}
}
}
private ChangeCache changeCache = new();
public bool Link(string directory, DelegateFileChange? fileChange = null)
{
bool result = false;
try
{
if (Directory.Exists(directory))
{
watcher = new FileSystemWatcher(directory);
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Changed += Watcher_Changed;
onChange = fileChange;
watcher.Filter = "*.*";
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
result = true;
}
}
catch (Exception)
{
}
return result;
}
private void OnStableChange(string path)
{
if (File.Exists(path))
{
onChange?.Invoke(path);
}
}
public void Watcher_Changed(object sender, FileSystemEventArgs e)
{
changeCache.Set(e.FullPath, CacheTimeMilliseconds, OnStableChange);
}
}
主要是为了未来的我:)
我用Rx写了一个包装器:
public class WatcherWrapper : IDisposable
{
private readonly FileSystemWatcher _fileWatcher;
private readonly Subject<FileSystemEventArgs> _infoSubject;
private Subject<FileSystemEventArgs> _eventSubject;
public WatcherWrapper(string path, string nameFilter = "*.*", NotifyFilters? notifyFilters = null)
{
_fileWatcher = new FileSystemWatcher(path, nameFilter);
if (notifyFilters != null)
{
_fileWatcher.NotifyFilter = notifyFilters.Value;
}
_infoSubject = new Subject<FileSystemEventArgs>();
_eventSubject = new Subject<FileSystemEventArgs>();
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Changed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Created").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Deleted").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
Observable.FromEventPattern<FileSystemEventArgs>(_fileWatcher, "Renamed").Select(e => e.EventArgs)
.Subscribe(_infoSubject.OnNext);
// this takes care of double events and still works with changing the name of the same file after a while
_infoSubject.Buffer(TimeSpan.FromMilliseconds(20))
.Select(x => x.GroupBy(z => z.FullPath).Select(z => z.LastOrDefault()).Subscribe(
infos =>
{
if (infos != null)
foreach (var info in infos)
{
{
_eventSubject.OnNext(info);
}
}
});
_fileWatcher.EnableRaisingEvents = true;
}
public IObservable<FileSystemEventArgs> FileEvents => _eventSubject;
public void Dispose()
{
_fileWatcher?.Dispose();
_eventSubject.Dispose();
_infoSubject.Dispose();
}
}
用法:
var watcher = new WatcherWrapper(_path, "*.info");
// all more complicated and scenario specific filtering of events can be done here
watcher.FileEvents.Where(x => x.ChangeType != WatcherChangeTypes.Deleted).Subscribe(x => //do stuff)
我认为解决这个问题的最佳方案是使用响应式扩展
当你将事件转换为可观察对象时,你可以添加Throttling(..)(最初称为Debounce(..))
这里是示例代码
var templatesWatcher = new FileSystemWatcher(settingsSnapshot.Value.TemplatesDirectory)
{
NotifyFilter = NotifyFilters.LastWrite,
IncludeSubdirectories = true
};
templatesWatcher.EnableRaisingEvents = true;
Observable.FromEventPattern<FileSystemEventHandler, FileSystemEventArgs>(
addHandler => templatesWatcher.Changed += addHandler,
removeHandler => templatesWatcher.Changed -= removeHandler)
.Throttle(TimeSpan.FromSeconds(5))
.Subscribe(args =>
{
_logger.LogInformation($"Template file {args.EventArgs.Name} has changed");
//TODO do something
});