我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用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);
}
}
我必须结合以上文章中的几个想法,并添加文件锁定检查,让它为我工作:
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
}
}
解决方案实际上取决于用例。您是否在注意不更改的新文件,或每隔一段时间更改一次的文件或经常更改的文件?在我的情况下,它的变化不太频繁,我不想错过任何这些变化。
但是我也不想在写入过程尚未完成写入的地方发生更改事件。
在我的情况下,我注意到6 (6 !!)onchange事件写一个125字符的txt文件。
我的解决方案是民意调查和改变事件的混合,民意调查经常被消极地看待。正常轮询比较慢,比如每10秒一次,以防FileSystemWatcher (FSW)“错过”一个事件。轮询立即响应FSW更改事件。
关键是在FSW。更改事件时,轮询速度加快,例如每100毫秒,并等待直到文件稳定。因此我们有了“两阶段轮询”:阶段1比较慢,但在FSW文件更改事件时立即响应。第二阶段是快速的,等待一个稳定的文件。
如果FSW检测到多个文件更改,每个事件都会加速轮询循环,并有效地启动一个新的短等待周期。只有在轮询循环检测到上次写入时文件没有进一步的变化之后,它才假定文件是稳定的,并且您的代码可以处理更改后的文件。
我选择了10秒和100毫秒的超时,但是您的用例可能需要不同的超时值。
这里是轮询,其中AppConfig。fiIO是要注意的FileInfo:
private readonly EventWaitHandle ewhTimeout = new AutoResetEvent(false);
private void TwoPhasedPolling()
{
bool WaitForChange = true; //false: wait until stable
DateTime LastWriteTime = DateTime.MinValue;
while (true)
{
// wait for next poll (timeout), or FSW event
bool GotOne = ewhTimeout.WaitOne(WaitForChange ? 10 * 1000 : 100);
if (GotOne)
{
// WaitOne interrupted: end of Phase1: FSW detected file change
WaitForChange = false;
}
else
{
// WaitOne timed out: Phase2: check file write time for change
if (AppConfig.fiIO.LastWriteTime > LastWriteTime)
{
LastWriteTime = AppConfig.fiIO.LastWriteTime;
}
else
{
// End of Phase2: file has changed and is stable
WaitForChange = true;
// action on changed file
... your code here ...
}}}}
private void fileSystemWatcher1_Changed(object sender, FileSystemEventArgs e)
{
ewhTimeout.Set();
}
NB:是的,我也不喜欢}}}},但它使列表更短,这样你就不必滚动了:-)