FileSystemWatcher更改事件被引发两次

我有一个应用程序,我正在寻找一个文本文件,如果有任何更改的文件,我正在使用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并保存它。

恐怕这是FileSystemWatcher类的一个众所周知的bug /function。 这是来自该课程的文档:

您可能会注意到在某些情况下,单个创build事件会生成由组件处理的多个创build事件。 例如,如果使用FileSystemWatcher组件监视目录中新文件的创build,然后使用记事本testing它以创build文件,则即使只创build了一个文件,也可能会看到两个创build的事件。 这是因为记事本在写入过程中执行多个文件系统操作。 记事本批量写入磁盘,创build文件的内容,然后创build文件属性。 其他应用程序可能以相同的方式执行。 由于FileSystemWatcher监视操作系统活动,所以这些应用程序触发的所有事件都将被拾取。

现在这一点文本是关于Created事件,但同样的事情也适用于其他文件事件。 在某些应用程序中,您可以通过使用NotifyFilter属性来解决此问题,但是我的经验是,有时您还必须执行一些手动重复筛选(黑客)。

前一阵子,我用一些FileSystemWatcher提示标记了一个页面。 你可能想看看。

我在委托中使用以下策略“解决了”这个问题:

 // fsw_ is the FileSystemWatcher instance used by my application. private void OnDirectoryChanged(...) { try { fsw_.EnableRaisingEvents = false; /* do my stuff once asynchronously */ } finally { fsw_.EnableRaisingEvents = true; } } 

通过检查相关文件上的File.GetLastWriteTime时间戳,可以检测到任何来自FileSystemWatcher重复OnChanged事件并将其丢弃。 像这样:

 DateTime lastRead = DateTime.MinValue; void OnChanged(object source, FileSystemEventArgs a) { DateTime lastWriteTime = File.GetLastWriteTime(uri); if (lastWriteTime != lastRead) { doStuff(); lastRead = lastWriteTime; } // else discard the (duplicated) OnChanged event } 

这是我的解决scheme,帮助我阻止事件两次提出:

 watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size; 

在这里我已经设置了NotifyFilter属性只有文件名和大小。
watcher是我FileSystemWatcher的对象。 希望这会有所帮助。

我的情况是我有一个虚拟机,里面有一个Linux服务器。 我正在Windows主机上开发文件。 当我更改主机上的文件夹中的某些内容时,我希望所有更改都能上传,并通过Ftp同步到虚拟服务器上。 当我写入文件(标记包含要修改的文件的文件夹)时,我将这样做消除重复的更改事件:

 private Hashtable fileWriteTime = new Hashtable(); private void fsw_sync_Changed(object source, FileSystemEventArgs e) { string path = e.FullPath.ToString(); string currentLastWriteTime = File.GetLastWriteTime( e.FullPath ).ToString(); // if there is no path info stored yet // or stored path has different time of write then the one now is inspected if ( !fileWriteTime.ContainsKey(path) || fileWriteTime[path].ToString() != currentLastWriteTime ) { //then we do the main thing log( "A CHANGE has occured with " + path ); //lastly we update the last write time in the hashtable fileWriteTime[path] = currentLastWriteTime; } } 

主要是我创build一个散列表来存储文件写入时间信息。 然后,如果散列表的文件path被修改,它的时间值与当前通知文件的更改相同,那么我知道这是事件的副本,并忽略它。

这是我的方法:

 // Consider having a List<String> named _changedFiles private void OnChanged(object source, FileSystemEventArgs e) { lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } } // do your stuff System.Timers.Timer timer = new Timer(1000) { AutoReset = false }; timer.Elapsed += (timerElapsedSender, timerElapsedArgs) => { lock (_changedFiles) { _changedFiles.Remove(e.FullPath); } }; timer.Start(); } 

这是我用来解决这个问题的解决scheme,我把这个文件作为邮件的附件发送。 即使采用较小的定时器间隔,也可以轻松避免两次触发的事件,但在我的情况下,1000是没问题的,因为我没有做任何改动,而是比每秒超过1条消息的邮箱更加幸运。 至less它可以正常工作,以防几个文件在同一时间被更改。

我想过的另一个解决scheme是用字典映射文件replace列表到他们各自的MD5,所以你不必select一个任意的时间间隔,因为你不必删除条目,而是更新它的值,取消你的东西,如果没有改变。 当文件被监视并且占用越来越多的内存时,它有一个不断增长的内存字典的缺点,但是我已经在某处看到监视的文件数量取决于FSW的内部缓冲区,所以可能不那么重要。 不知道MD5的计算时间会如何影响你的代码的性能,小心=

试试这个代码:

 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; } } 

我知道这是一个老问题,但也有同样的问题,上面的解决scheme都没有真正解决我面临的问题。 我创build了一个将LastWriteTime映射到文件名的字典。 因此,如果文件不在字典中,将继续进行其他明智的检查,看看是什么时候是最后修改的时间,如果不同于它是在字典中运行的代码。

  Dictionary<string, DateTime> dateTimeDictionary = new Dictionary<string, DateTime>(); private void OnChanged(object source, FileSystemEventArgs e) { if (!dateTimeDictionary.ContainsKey(e.FullPath) || (dateTimeDictionary.ContainsKey(e.FullPath) && System.IO.File.GetLastWriteTime(e.FullPath) != dateTimeDictionary[e.FullPath])) { dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath); //your code here } } 

一个可能的“黑客”就是使用Reactive Extensions来节制事件,例如:

 var watcher = new FileSystemWatcher("./"); Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed") .Throttle(new TimeSpan(500000)) .Subscribe(HandleChangeEvent); watcher.EnableRaisingEvents = true; 

在这种情况下,我将节stream到50ms,在我的系统上已经足够了,但是更高的值应该更安全。 (就像我说的,这仍然是一个'黑客')。

主要原因是第一个事件的上次访问时间是当前时间(文件写入或更改时间)。 那么第二个事件就是文件原始的上次访问时间。 我在代码下解决。

  var lastRead = DateTime.MinValue; Watcher = new FileSystemWatcher(...) { NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite, Filter = "*.dll", IncludeSubdirectories = false, }; Watcher.Changed += (senderObject, ea) => { var now = DateTime.Now; var lastWriteTime = File.GetLastWriteTime(ea.FullPath); if (now == lastWriteTime) { return; } if (lastWriteTime != lastRead) { // do something... lastRead = lastWriteTime; } }; Watcher.EnableRaisingEvents = true; 

我花了大量的时间使用FileSystemWatcher,这里的一些方法将无法正常工作。 我真的很喜欢禁用事件的方法,但不幸的是,如果有大于1个文件被丢弃,第二个文件将被忽略,如果不是所有的时间。 所以我使用以下方法:

 private void EventCallback(object sender, FileSystemEventArgs e) { var fileName = e.FullPath; if (!File.Exists(fileName)) { // We've dealt with the file, this is just supressing further events. return; } // File exists, so move it to a working directory. File.Move(fileName, [working directory]); // Kick-off whatever processing is required. } 

我在这里有一个非常快速和简单的解决方法,它对我有用,无论事件会偶尔触发一次还是两次或更多次,请检查:

 private int fireCount = 0; private void inputFileWatcher_Changed(object sender, FileSystemEventArgs e) { fireCount++; if (fireCount == 1) { MessageBox.Show("Fired only once!!"); dowork(); } else { fireCount = 0; } } } 

此代码为我工作。

  private void OnChanged(object source, FileSystemEventArgs e) { string fullFilePath = e.FullPath.ToString(); string fullURL = buildTheUrlFromStudyXML(fullFilePath); System.Diagnostics.Process.Start("iexplore", fullURL); Timer timer = new Timer(); ((FileSystemWatcher)source).Changed -= new FileSystemEventHandler(OnChanged); timer.Interval = 1000; timer.Elapsed += new ElapsedEventHandler(t_Elapsed); timer.Start(); } private void t_Elapsed(object sender, ElapsedEventArgs e) { ((Timer)sender).Stop(); theWatcher.Changed += new FileSystemEventHandler(OnChanged); } 

这是一个可以尝试的新解决scheme。 适合我。 在更改事件的事件处理程序中,如果需要,可以通过编程方式从devise器中删除处理程序,然后以编程方式将处理程序添加回去。 例:

 public void fileSystemWatcher1_Changed( object sender, System.IO.FileSystemEventArgs e ) { this.fileSystemWatcher1.Changed -= new System.IO.FileSystemEventHandler( this.fileSystemWatcher1_Changed ); MessageBox.Show( "File has been uploaded to destination", "Success!" ); this.fileSystemWatcher1.Changed += new System.IO.FileSystemEventHandler( this.fileSystemWatcher1_Changed ); } 

我改变了监视目录中文件的方式。 而不是使用FileSystemWatcher轮询另一个线程上的位置,然后查看该文件的LastWriteTime。

 DateTime lastWriteTime = File.GetLastWriteTime(someFilePath); 

使用这些信息并保留一个文件path的索引和最新的写入时间,我可以确定已经改变的文件或者已经在特定位置创build的文件。 这使我从FileSystemWatcher的怪异中解脱出来。 主要的缺点是你需要一个数据结构来存储LastWriteTime和对文件的引用,但是它是可靠和易于实现的。

您可以尝试打开它进行写入,如果成功,则可以假设其他应用程序已经完成了该文件。

 private void OnChanged(object source, FileSystemEventArgs e) { try { using (var fs = File.OpenWrite(e.FullPath)) { } //do your stuff } catch (Exception) { //no write access, other app not done } } 

只要打开它写入似乎不会引起更改的事件。 所以它应该是安全的。

对不起,我们一直在争论这个问题,现在终于想出了一个办法来处理这些多发的事件。 我想感谢这个话题中的每个人,因为我曾经在许多参考文献中使用过这个问题。

这是我完整的代码。 它使用一个字典来跟踪上次写入文件的date和时间。 它比较这个价值,如果它是相同的,它就会压制事件。 然后在启动新线程之后设置该值。

 using System.Threading; // used for backgroundworker using System.Diagnostics; // used for file information private static IDictionary<string, string> fileModifiedTable = new Dictionary<string, string>(); // used to keep track of our changed events private void fswFileWatch_Changed( object sender, FileSystemEventArgs e ) { try { //check if we already have this value in our dictionary. if ( fileModifiedTable.TryGetValue( e.FullPath, out sEmpty ) ) { //compare timestamps if ( fileModifiedTable[ e.FullPath ] != File.GetLastWriteTime( e.FullPath ).ToString() ) { //lock the table lock ( fileModifiedTable ) { //make sure our file is still valid if ( File.Exists( e.FullPath ) ) { // create a new background worker to do our task while the main thread stays awake. Also give it do work and work completed handlers BackgroundWorker newThreadWork = new BackgroundWorker(); newThreadWork.DoWork += new DoWorkEventHandler( bgwNewThread_DoWork ); newThreadWork.RunWorkerCompleted += new RunWorkerCompletedEventHandler( bgwNewThread_RunWorkerCompleted ); // capture the path string eventFilePath = e.FullPath; List<object> arguments = new List<object>(); // add arguments to pass to the background worker arguments.Add( eventFilePath ); arguments.Add( newEvent.File_Modified ); // start the new thread with the arguments newThreadWork.RunWorkerAsync( arguments ); fileModifiedTable[ e.FullPath ] = File.GetLastWriteTime( e.FullPath ).ToString(); //update the modified table with the new timestamp of the file. FILE_MODIFIED_FLAG.WaitOne(); // wait for the modified thread to complete before firing the next thread in the event multiple threads are being worked on. } } } } } catch ( IOException IOExcept ) { //catch any errors postError( IOExcept, "fswFileWatch_Changed" ); } } 

我创build了一个扩展FileSystemWatcher的类,只有在完成复制时触发事件的Git仓库。 它丢弃所有改变的事件,并且只有当文件变为可读时才会引发它。

下载FileSystemSafeWatcher并将其添加到您的项目。

然后将其用作正常的FileSystemWatcher并在事件触发时进行监视。

 var fsw = new FileExamSystemWatcher(file); fsw.EnableRaisingEvents = true; // Add event handlers here fsw.Created += fsw_Created; 

我能够通过添加一个函数来检查缓冲区数组中的重复项。

然后在使用定时器X数组未被修改之后执行操作: – 每次将某些内容写入缓冲区时重置定时器 – 在tick上执行操作

这也捕获了另一种重复types。 如果您修改文件夹内的文件,该文件夹还会引发更改事件。

 Function is_duplicate(str1 As String) As Boolean If lb_actions_list.Items.Count = 0 Then Return False Else Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then Return False Else Return True End If End If End Function Public Module extentions <Extension()> Public Function parentDir(ByVal aString As String) As String Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1))) End Function End Module 
 FileReadTime = DateTime.Now; private void File_Changed(object sender, FileSystemEventArgs e) { var lastWriteTime = File.GetLastWriteTime(e.FullPath); if (lastWriteTime.Subtract(FileReadTime).Ticks > 0) { // code FileReadTime = DateTime.Now; } } 

这个解决scheme适用于生产应用程序:

环境:

VB.Net框架4.5.2

手动设置对象属性:NotifyFilter = Size

然后使用这个代码:

 Public Class main Dim CalledOnce = False Private Sub FileSystemWatcher1_Changed(sender As Object, e As IO.FileSystemEventArgs) Handles FileSystemWatcher1.Changed If (CalledOnce = False) Then CalledOnce = True If (e.ChangeType = 4) Then ' Do task... CalledOnce = False End If End Sub End Sub 

尝试这个!

 string temp=""; 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) { ....... if(temp=="") { //do thing you want. temp = e.name //name of text file. }else if(temp !="" && temp != e.name) { //do thing you want. temp = e.name //name of text file. }else { //second fire ignored. } } 

主要是为了未来我:)

我用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) 

如果您注册到OnChanged事件,那么只要您只需要监视OnChange事件就可以在更改之前删除监视的文件。

简单的定义一个全局variablesvar1 = true

 Private Sub FileWatchman_Changed(ByVal sender As System.Object, ByVal e As System.IO.FileSystemEventArgs) Handles FileWatchman.Changed If var1 = true your logic goes here var1 = false Else var1 = true End If End Sub 

那么,这里是我的解决scheme如何提高事件只有一次:

 FileSystemWatcheк watcher = new FileSystemWatcher(); //'path' - path to the file that has been modified. watcher.Changed += (s, e) => FileChanged(path); 

here is implementation of FileChanged

 //count is our counter to triger when we can raise and when not. private int count = 0; private void FileChanged(string path) { if (count % 2 == 0) { //code here } count ++; }