FileSystemWatcher Changed事件被引发两次

我有一个应用程序,我正在寻找一个文本文件,如果文件有任何更改,我将使用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)
{
.......
}

在我的例子中,当我更改文本文件version.txt并保存它时,OnChanged被调用了两次。

158579 次浏览

恐怕这是FileSystemWatcher类的一个众所周知的错误/特性。这是来自类的文档:

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

现在这段文本是关于Created事件的,但同样的事情也适用于其他文件事件。在一些应用程序中,你可能可以通过使用NotifyFilter属性来解决这个问题,但我的经验是,有时你还必须做一些手动的重复过滤(hacks)。

前段时间我用一些FileSystemWatcher技巧标记了一个页面。你可能会想去看看。

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

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

使用这些信息并保持文件路径的索引和最近的写入时间,我可以确定在特定位置已更改或已创建的文件。这使我摆脱了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
}
}

仅仅打开它以供写入似乎不会引发已更改的事件。所以应该是安全的。

我已经在我的委托中使用以下策略“修复”了这个问题:

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

任何来自FileSystemWatcher的重复OnChanged事件都可以通过检查有关文件的File.GetLastWriteTime时间戳来检测和丢弃。像这样:

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
}

如果你注册了OnChanged事件,那么在修改之前删除被监视的文件可能会起作用,只要你只需要监视OnChange事件。

我的场景是,我有一个虚拟机,其中有一个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;
}
}

我主要创建一个哈希表来存储文件写时间信息。然后,如果哈希表有被修改的文件路径,并且它的时间值与当前通知的文件更改相同,那么我就知道它是事件的副本,并忽略它。

以下是我的方法:

// Consider having a List<String> named _changedFiles


private void OnChanged(object source, FileSystemEventArgs e)
{
lock (_changedFiles)
{
if (_changedFiles.Contains(e.FullPath))
{
return;
}
_changedFiles.Add(e.FullPath);
}


// do your stuff


System.Timers.Timer timer = new Timer(1000) { AutoReset = false };
timer.Elapsed += (timerElapsedSender, timerElapsedArgs) =>
{
lock (_changedFiles)
{
_changedFiles.Remove(e.FullPath);
}
};
timer.Start();
}

这是我用来解决这个问题的解决方案,在一个项目中,我将文件作为附件在邮件中发送。 它可以很容易地避免两次触发事件,即使是一个较小的定时器间隔,但在我的情况下,1000是可以的,因为我更喜欢错过一些变化,而不是每秒用> 1条消息淹没邮箱。 至少在几个文件同时被更改的情况下,它工作得很好 我想到的另一个解决方案是用一个字典来替换列表,将文件映射到它们各自的MD5,这样你就不必选择任意的间隔,因为你不必删除条目,而是更新它的值,如果它没有改变,就取消你的东西。 它的缺点是,随着文件被监视和占用越来越多的内存,Dictionary在内存中不断增长,但我在某处读到过,监视的文件数量取决于FSW的内部缓冲区,所以可能不是那么重要。 也不知道MD5计算时间会如何影响代码的性能,小心=\

好吧,这是我如何只引发一个事件的解决方案:

FileSystemWatcheк watcher = new FileSystemWatcher();


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

这里是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 ++;
}

以下是我的解决方案,帮助我阻止事件被提出两次:

watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.Size;
这里我只设置了NotifyFilter属性的文件名和大小 watcher是FileSystemWatcher的对象。

.

.

简单地定义一个全局变量var1 = 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

一个可能的“黑客”是使用响应式扩展来限制事件,例如:

var watcher = new FileSystemWatcher("./");


Observable.FromEventPattern<FileSystemEventArgs>(watcher, "Changed")
.Throttle(new TimeSpan(500000))
.Subscribe(HandleChangeEvent);


watcher.EnableRaisingEvents = true;

在本例中,我将设置为50ms,在我的系统上这就足够了,但更高的值应该更安全。(就像我说的,这仍然是一个“hack”)。

我已经创建了一个Git repo,类扩展了FileSystemWatcher,仅在复制完成时触发事件。它将丢弃除最后一个事件以外的所有已更改事件,仅在文件可读时才引发该事件。

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

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

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

我可以通过添加一个函数来检查缓冲区数组中的重复项来做到这一点。

然后使用定时器在数组X时间内未被修改后执行该操作: -每次有东西写入缓冲区时重置计时器 —对tick

执行动作

这还捕获了另一种复制类型。如果修改文件夹内的文件,该文件夹也会引发Change事件。

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;
}
}
主要原因是 第一个事件的最后一次访问时间是当前时间(文件写入或更改时间)。 第二个事件是文件最初的最后一次访问时间。

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

我知道这是一个老问题,但我遇到了同样的问题,上面的解决方案都没有真正解决我所面临的问题。我已经创建了一个字典,它将文件名与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
}
}

试试下面的代码:

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

这个代码对我有用。

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

很抱歉挖了坟墓,但我已经与这个问题斗争了一段时间,终于想出了一种方法来处理这些多重发射事件。我想要感谢这篇文章中的每一个人,因为我在与这个问题作斗争时已经在许多参考文献中使用了它。

这是我的完整代码。它使用字典来跟踪文件最后一次写入的日期和时间。它比较该值,如果值相同,则抑制事件。然后在启动新线程后设置该值。

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

这里有一个你可以尝试的新解决方案。很适合我。在已更改事件的事件处理程序中,以编程方式从设计器输出中删除处理程序(如果需要的话),然后以编程方式将处理程序添加回来。例子:

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

这个解决方案适用于我的生产应用程序:

环境:

VB。Net Framework 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)

我必须结合以上文章中的几个想法,并添加文件锁定检查,让它为我工作:

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

事件如果没有被问到,很遗憾没有现成的f#解决方案样本。 为了解决这个问题,这里是我的配方,因为我可以,而且f#是一种很棒的。net语言

使用FSharp.Control.Reactive包过滤掉重复的事件,它只是响应式扩展的f#包装器。所有这些都可以针对整个框架或netstandard2.0:

let createWatcher path filter () =
new FileSystemWatcher(
Path = path,
Filter = filter,
EnableRaisingEvents = true,
SynchronizingObject = null // not needed for console applications
)


let createSources (fsWatcher: FileSystemWatcher) =
// use here needed events only.
// convert `Error` and `Renamed` events to be merded
[| fsWatcher.Changed :> IObservable<_>
fsWatcher.Deleted :> IObservable<_>
fsWatcher.Created :> IObservable<_>
//fsWatcher.Renamed |> Observable.map renamedToNeeded
//fsWatcher.Error   |> Observable.map errorToNeeded
|] |> Observable.mergeArray


let handle (e: FileSystemEventArgs) =
printfn "handle %A event '%s' '%s' " e.ChangeType e.Name e.FullPath


let watch path filter throttleTime =
// disposes watcher if observer subscription is disposed
Observable.using (createWatcher path filter) createSources
// filter out multiple equal events
|> Observable.distinctUntilChanged
// filter out multiple Changed
|> Observable.throttle throttleTime
|> Observable.subscribe handle


[<EntryPoint>]
let main _args =
let path = @"C:\Temp\WatchDir"
let filter = "*.zip"
let throttleTime = TimeSpan.FromSeconds 10.
use _subscription = watch path filter throttleTime
System.Console.ReadKey() |> ignore
0 // return an integer exit code

我是这样处理双重创造问题的,忽略了第一个事件:

Private WithEvents fsw As New System.IO.FileSystemWatcher
Private complete As New List(Of String)


Private Sub fsw_Created(ByVal sender As Object, _
ByVal e As System.IO.FileSystemEventArgs) Handles fsw.Created


If Not complete.Contains(e.FullPath) Then
complete.Add(e.FullPath)


Else
complete.Remove(e.FullPath)
Dim th As New Threading.Thread(AddressOf hprocess)
th.Start(e)


End If


End Sub
这些答案很多都令人震惊,真的。 下面是我的XanderUI控件库中的一些代码,可以修复这个问题
private void OnChanged(object sender, FilesystemEventArgs e)
{
if (FSWatcher.IncludeSubdirectories == true)
{
if (File.Exists(e.FullPath)) { DO YOUR FILE CHANGE STUFF HERE... }
}
else DO YOUR DIRECTORY CHANGE STUFF HERE...
}

我简单地添加了一个dupe检查如下:

 private void OnChanged(object source, FileSystemEventArgs e)
{
string sTabName = Path.GetFileNameWithoutExtension(e.Name);
string sLastLine = ReadLastLine(e.FullPath);
if(sLastLine != _dupeCheck)
{
TabPage tp = tcLogs.TabPages[sTabName];
TextBox tbLog = (TextBox)tp.Controls[0] as TextBox;


tbLog.Invoke(new Action(() => tbLog.AppendText(sLastLine + Environment.NewLine)));
tbLog.Invoke(new Action(() => tbLog.SelectionStart = tbLog.Text.Length));
tbLog.Invoke(new Action(() => tbLog.ScrollToCaret()));
_dupeCheck = sLastLine;
}
}


public static String ReadLastLine(string path)
{
return ReadLastLine(path, Encoding.Default, "\n");
}


public static String ReadLastLine(string path, Encoding encoding, string newline)
{
int charsize = encoding.GetByteCount("\n");
byte[] buffer = encoding.GetBytes(newline);
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
long endpos = stream.Length / charsize;
for (long pos = charsize; pos < endpos; pos += charsize)
{
stream.Seek(-pos, SeekOrigin.End);
stream.Read(buffer, 0, buffer.Length);
if (encoding.GetString(buffer) == newline)
{
buffer = new byte[stream.Length - stream.Position];
stream.Read(buffer, 0, buffer.Length);
return encoding.GetString(buffer);
}
}
}
return null;
}


[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SendMessage(IntPtr hWnd, int wMsg, IntPtr wParam, IntPtr lParam);


private const int WM_VSCROLL = 0x115;
private const int SB_BOTTOM = 7;


/// <summary>
/// Scrolls the vertical scroll bar of a multi-line text box to the bottom.
/// </summary>
/// <param name="tb">The text box to scroll</param>
public static void ScrollToBottom(TextBox tb)
{
SendMessage(tb.Handle, WM_VSCROLL, (IntPtr)SB_BOTTOM, IntPtr.Zero);
}

在我的情况下,需要得到由其他应用程序插入的文本文件的最后一行,只要插入完成。这是我的解决方案。当第一个事件被引发时,我禁止观察者引发其他人,然后我调用定时器TimeElapsedEvent,因为当我的句柄函数OnChanged被调用时,我需要文本文件的大小,但当时的大小不是实际的大小,它是插入之前文件的大小。所以我等待了一段时间来处理正确的文件大小。

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

试试这个,效果很好

  private static readonly FileSystemWatcher Watcher = new FileSystemWatcher();
static void Main(string[] args)
{
Console.WriteLine("Watching....");


Watcher.Path = @"D:\Temp\Watcher";
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
Console.ReadKey();
}


static void OnChanged(object sender, FileSystemEventArgs e)
{
try
{
Watcher.Changed -= OnChanged;
Watcher.EnableRaisingEvents = false;
Console.WriteLine($"File Changed. Name: {e.Name}");
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
finally
{
Watcher.Changed += OnChanged;
Watcher.EnableRaisingEvents = true;
}
}

代码可自定义禁用阻止第二个观察者升起的时间间隔,如果他们存在,则不阻止观察者:

    namespace Watcher
{
class Static
{
public static DateTime lastDomain { get; set; }
public static string lastDomainStr { get; set; }
}
public partial class Form1 : Form
{
int minMs = 20;//time for blocking in ms
public Form1()
{
InitializeComponent();
Static.lastDomain = new DateTime(1970, 1, 1, 0, 0, 0);
Static.lastDomainStr = "";
Start();
}
private void Start()//Start watcher
{
//...
domain.Changed += new FileSystemEventHandler(Domain);
domain.EnableRaisingEvents = true;
//...you second unblocked watchers
second.Changed += new FileSystemEventHandler(Second);
second.EnableRaisingEvents = true;
}
private void Domain(object source, FileSystemEventArgs e)
{
if (now.Subtract(Static.lastDomain).TotalMilliseconds < minMs && Static.lastDomainStr == e.FullPath)return;
//...you code here
/* if you need form access
this.Invoke(new MethodInvoker(() =>{ textBox1.Text = "...";}));
*/
Static.lastDomain = DateTime.Now;
Static.lastDomainStr = e.FullPath;
}
private void Second(object source, FileSystemEventArgs e)
{
//...Second rised
}
}
}

我只想对最后一个事件做出反应,以防万一,也对linux文件更改,它似乎在第一次调用时文件是空的,然后在下一个调用时再次填充,不介意浪费一些时间,以防操作系统决定做一些文件/属性更改。

我在这里使用。net async来帮助我做线程。

    private static int _fileSystemWatcherCounts;
private async void OnChanged(object sender, FileSystemEventArgs e)
{
// Filter several calls in short period of time
Interlocked.Increment(ref _fileSystemWatcherCounts);
await Task.Delay(100);
if (Interlocked.Decrement(ref _fileSystemWatcherCounts) == 0)
DoYourWork();
}

这是另一种方法。现在,除了最后一个事件以外,所有事件都被抑制,而不是传播一系列事件中的第一个事件并抑制所有接下来的事件。我认为可以从这种方法中受益的场景更常见。

要做到这一点,我们必须使用滑动延迟。每个传入事件都会取消触发前一个事件的计时器,并重新启动计时器。这开启了一种可能性,即一系列永无止境的事件将永远推迟传播。为了简单起见,在下面的扩展方法中没有针对这种异常情况的规定。

public static class FileSystemWatcherExtensions
{
public static IDisposable OnAnyEvent(this FileSystemWatcher source,
WatcherChangeTypes changeTypes, FileSystemEventHandler handler, int delay)
{
var cancellations = new Dictionary<string, CancellationTokenSource>(
StringComparer.OrdinalIgnoreCase);
var locker = new object();
if (changeTypes.HasFlag(WatcherChangeTypes.Created))
source.Created += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Deleted))
source.Deleted += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Changed))
source.Changed += FileSystemWatcher_Event;
if (changeTypes.HasFlag(WatcherChangeTypes.Renamed))
source.Renamed += FileSystemWatcher_Event;
return new Disposable(() =>
{
source.Created -= FileSystemWatcher_Event;
source.Deleted -= FileSystemWatcher_Event;
source.Changed -= FileSystemWatcher_Event;
source.Renamed -= FileSystemWatcher_Event;
});


async void FileSystemWatcher_Event(object sender, FileSystemEventArgs e)
{
var key = e.FullPath;
var cts = new CancellationTokenSource();
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing))
{
existing.Cancel();
}
cancellations[key] = cts;
}
try
{
await Task.Delay(delay, cts.Token);
// Omitting ConfigureAwait(false) is intentional here.
// Continuing in the captured context is desirable.
}
catch (TaskCanceledException)
{
return;
}
lock (locker)
{
if (cancellations.TryGetValue(key, out var existing)
&& existing == cts)
{
cancellations.Remove(key);
}
}
cts.Dispose();
handler(sender, e);
}
}


public static IDisposable OnAllEvents(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.All, handler, delay);


public static IDisposable OnCreated(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Created, handler, delay);


public static IDisposable OnDeleted(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Deleted, handler, delay);


public static IDisposable OnChanged(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Changed, handler, delay);


public static IDisposable OnRenamed(this FileSystemWatcher source,
FileSystemEventHandler handler, int delay)
=> OnAnyEvent(source, WatcherChangeTypes.Renamed, handler, delay);


private struct Disposable : IDisposable
{
private readonly Action _action;
internal Disposable(Action action) => _action = action;
public void Dispose() => _action?.Invoke();
}
}

使用的例子:

myWatcher.OnAnyEvent(WatcherChangeTypes.Created | WatcherChangeTypes.Changed,
MyFileSystemWatcher_Event, 100);

这一行结合了对两个事件的订阅,CreatedChanged。所以它大致相当于这些:

myWatcher.Created += MyFileSystemWatcher_Event;
myWatcher.Changed += MyFileSystemWatcher_Event;

不同之处在于,这两个事件被视为单一类型的事件,在这些事件快速连续的情况下,只有最后一个事件将被传播。例如,如果一个Created事件后面跟着两个Changed事件,并且这三个事件之间的时间间隔不超过100 msec,则只有第二个Changed事件将通过调用MyFileSystemWatcher_Event处理程序来传播,而前一个事件将被丢弃。

我认为解决这个问题的最佳方案是使用响应式扩展 当你将事件转换为可观察对象时,你可以添加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
});

一直在寻找答案,但我想到了一个肮脏的解决方案。因为我的事件触发了两次,所以第二个动作什么也不做。

       $count = 1
$action = {
if($count -eq 1){
#DO SOMETHING
$count = 2
}else{
$count = 1
}
}

我用了n种更简单的方法。

  1. 布尔-如果正在进行某项操作,则为true。当它结束时,false。
  2. 在处理之前,将其添加到HashSet。这样我就不会重复元素了。
  3. 每隔30分钟,计时器的事件就会运行一次,如果没有作业正在执行,它就会清除列表(just hashset = new hashset)。

解决方案实际上取决于用例。您是否在注意不更改的新文件,或每隔一段时间更改一次的文件或经常更改的文件?在我的情况下,它的变化不太频繁,我不想错过任何这些变化。

但是我也不想在写入过程尚未完成写入的地方发生更改事件。

在我的情况下,我注意到6 (6 !!)onchange事件写一个125字符的txt文件。

我的解决方案是民意调查和改变事件的混合,民意调查经常被消极地看待。正常的轮询比较慢,比如每10秒一次,以防文件系统监视器(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:是的,我也不喜欢}}}},但它使列表更短,这样你就不必滚动了:-)

我们可以这么简单。这对我很管用。

private static void OnChanged(object sender, FileSystemEventArgs e)
{
if (File.GetAttributes(e.FullPath) == FileAttributes.Directory)
return;
double timeSpan = DateTime.Now.Subtract(File.GetLastWriteTime(e.FullPath)).TotalSeconds;
if (timeSpan > 1)
return;
Console.WriteLine($"Changed: {e.FullPath}");
}

我在此将这段代码遗赠后人:

    static DateTimeOffset lastChanged = DateTimeOffset.UtcNow;
static string lastChangedFile = null;


...


private static void OnChanged(object sender, FileSystemEventArgs e)
{
if (e.ChangeType != WatcherChangeTypes.Changed ||
(lastChanged.AddMilliseconds(500) > DateTimeOffset.UtcNow && lastChangedFile == e.FullPath)
)
{
return;
}
lastChanged = DateTimeOffset.UtcNow;
lastChangedFile = e.FullPath;
Console.WriteLine($"Changed: {e.FullPath}");
            

}

这已经很晚了,但我最近遇到了这个问题,然后我想发表我的一点贡献。

首先,许多建议的解决方案都适用于单个更新的文件,而我需要在较短的时间内(几十毫秒)收到关于2-3个更改文件的通知,而重复时间相对较长(几十秒到几分钟)。

早期建议的最有趣的链接之一是FileSystemWatcher有点坏。然而,建议的解决方案只有部分工作,正如同一作者在.NET内存缓存过期导致的不稳定行为被解密中指出的那样,即使在20秒之后也会给出通知。

然后,我所做的是基于类似的原则,设计一个愚蠢的替代解决方案,没有MemoryCache

基本上,它创建了一个项目的List<>,其中Key是文件的完整路径和一个过期计时器。如果另一个事件再次触发该更改,则在列表中找到该元素,计时器将更新为新的过期时间。 根据经验,过期时间足够长,可以在一个OnStableChange通知中收集多个事件,而不会太长,以至于感觉没有响应

当你实例化Whatever时,你也将它Link到一个目录和一个非常基本的外部回调。

没有什么是真正优化的,我只是寻找解决方案在几行

我把它发表在这里

  1. 对我来说,这样你就可以在另一个应用程序上验证
  2. 某个更聪明、更有经验的人可以改进它,并帮助我了解它的不足之处
    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);
}
}