我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用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并保存它。
试试下面的代码:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。
首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于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);
}
}
试试下面的代码:
class WatchPlotDirectory
{
bool let = false;
FileSystemWatcher watcher;
string path = "C:/Users/jamie/OneDrive/Pictures/Screenshots";
public WatchPlotDirectory()
{
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "*.*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
watcher.EnableRaisingEvents = true;
}
void OnChanged(object sender, FileSystemEventArgs e)
{
if (let==false) {
string mgs = string.Format("File {0} | {1}",
e.FullPath, e.ChangeType);
Console.WriteLine("onchange: " + mgs);
let = true;
}
else
{
let = false;
}
}
void OnRenamed(object sender, RenamedEventArgs e)
{
string log = string.Format("{0} | Renamed from {1}",
e.FullPath, e.OldName);
Console.WriteLine("onrenamed: " + log);
}
public void setPath(string path)
{
this.path = path;
}
}
我认为解决这个问题的最佳方案是使用响应式扩展
当你将事件转换为可观察对象时,你可以添加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
});
我必须结合以上文章中的几个想法,并添加文件锁定检查,让它为我工作:
FileSystemWatcher fileSystemWatcher;
private void DirectoryWatcher_Start()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher
{
Path = @"c:\mypath",
NotifyFilter = NotifyFilters.LastWrite,
Filter = "*.*",
EnableRaisingEvents = true
};
fileSystemWatcher.Changed += new FileSystemEventHandler(DirectoryWatcher_OnChanged);
}
private static void WaitUntilFileIsUnlocked(String fullPath, Action<String> callback, FileAccess fileAccess = FileAccess.Read, Int32 timeoutMS = 10000)
{
Int32 waitMS = 250;
Int32 currentMS = 0;
FileInfo file = new FileInfo(fullPath);
FileStream stream = null;
do
{
try
{
stream = file.Open(FileMode.Open, fileAccess, FileShare.None);
stream.Close();
callback(fullPath);
return;
}
catch (IOException)
{
}
finally
{
if (stream != null)
stream.Dispose();
}
Thread.Sleep(waitMS);
currentMS += waitMS;
} while (currentMS < timeoutMS);
}
private static Dictionary<String, DateTime> DirectoryWatcher_fileLastWriteTimeCache = new Dictionary<String, DateTime>();
private void DirectoryWatcher_OnChanged(Object source, FileSystemEventArgs ev)
{
try
{
lock (DirectoryWatcher_fileLastWriteTimeCache)
{
DateTime lastWriteTime = File.GetLastWriteTime(ev.FullPath);
if (DirectoryWatcher_fileLastWriteTimeCache.ContainsKey(ev.FullPath))
{
if (DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath].AddMilliseconds(500) >= lastWriteTime)
return; // file was already handled
}
DirectoryWatcher_fileLastWriteTimeCache[ev.FullPath] = lastWriteTime;
}
Task.Run(() => WaitUntilFileIsUnlocked(ev.FullPath, fullPath =>
{
// do the job with fullPath...
}));
}
catch (Exception e)
{
// handle exception
}
}