我有一个应用程序,我正在寻找一个文本文件,如果对文件做了任何更改,我使用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并保存它。
解决方案实际上取决于用例。您是否在注意不更改的新文件,或每隔一段时间更改一次的文件或经常更改的文件?在我的情况下,它的变化不太频繁,我不想错过任何这些变化。
但是我也不想在写入过程尚未完成写入的地方发生更改事件。
在我的情况下,我注意到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:是的,我也不喜欢}}}},但它使列表更短,这样你就不必滚动了:-)
In my case need to get the last line of a text file that is inserted by other application, as soon as insertion is done. Here is my solution. When the first event is raised, i disable the watcher from raising others, then i call the timer TimeElapsedEvent because when my handle function OnChanged is called i need the size of the text file, but the size at that time is not the actual size, it is the size of the file imediatelly before the insertion. So i wait for a while to proceed with the right file size.
private FileSystemWatcher watcher = new FileSystemWatcher();
...
watcher.Path = "E:\\data";
watcher.NotifyFilter = NotifyFilters.LastWrite ;
watcher.Filter = "data.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
...
private void OnChanged(object source, FileSystemEventArgs e)
{
System.Timers.Timer t = new System.Timers.Timer();
try
{
watcher.Changed -= new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = false;
t.Interval = 500;
t.Elapsed += (sender, args) => t_Elapsed(sender, e);
t.Start();
}
catch(Exception ex) {
;
}
}
private void t_Elapsed(object sender, FileSystemEventArgs e)
{
((System.Timers.Timer)sender).Stop();
//.. Do you stuff HERE ..
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。
首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于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);
}
}