Ich habe eine Anwendung, in der ich nach einer Textdatei suche. Wenn Änderungen an der Datei vorgenommen werden, verwende ich den OnChanged
Eventhandler, um das Ereignis zu behandeln. Ich benutze das, NotifyFilters.LastWriteTime
aber das Ereignis wird immer noch zweimal ausgelöst. Hier ist der Code.
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)
{
.......
}
In meinem Fall OnChanged
wird das zweimal aufgerufen, wenn ich die Textdatei ändere version.txt
und speichere.
c#
filesystemwatcher
user214707
quelle
quelle
Antworten:
Ich befürchte, dass dies ein bekannter Fehler / eine bekannte Funktion der
FileSystemWatcher
Klasse ist. Dies ist aus der Dokumentation der Klasse:In diesem Text geht es um das
Created
Ereignis, aber das Gleiche gilt auch für andere Dateiereignisse. In einigen Anwendungen können Sie dies möglicherweise mithilfe von umgehenNotifyFilter
Eigenschaft umgehen, aber meiner Erfahrung nach müssen Sie manchmal auch einige manuelle Doppelfilterungen (Hacks) durchführen.Vor einiger Zeit habe ich eine Seite mit ein paar FileSystemWatcher-Tipps gebucht . Vielleicht möchten Sie es überprüfen.
quelle
Ich habe dieses Problem mit der folgenden Strategie in meinem Delegaten "behoben":
quelle
Alle doppelten
OnChanged
Ereignisse aus demFileSystemWatcher
können erkannt und verworfen werden, indem derFile.GetLastWriteTime
Zeitstempel in der betreffenden Datei überprüft wird . Wie so:quelle
"Rename"
den Namen des Ereignisses, an dem Sie interessiert sind):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
DateTime
nur Millisekundenauflösung hat, funktioniert diese Methode auch wenn Sie ersetzenFile.GetLastWriteTime
mitDateTime.Now
. Abhängig von Ihrer Situation können Sie auch diea.FullName
in einer globalen Variablen verwenden, um doppelte Ereignisse zu erkennen.Hier ist meine Lösung, die mir geholfen hat, zu verhindern, dass das Ereignis zweimal ausgelöst wird:
Hier habe ich die
NotifyFilter
Eigenschaft nur mit Dateiname und Größe festgelegt.watcher
ist mein Objekt von FileSystemWatcher. Hoffe das wird helfen.quelle
Mein Szenario ist, dass ich eine virtuelle Maschine mit einem Linux-Server habe. Ich entwickle Dateien auf dem Windows-Host. Wenn ich etwas in einem Ordner auf dem Host ändere, möchte ich, dass alle Änderungen hochgeladen und über Ftp auf den virtuellen Server synchronisiert werden. So eliminiere ich das doppelte Änderungsereignis, wenn ich in eine Datei schreibe (die auch den Ordner kennzeichnet, der die zu ändernde Datei enthält):
Hauptsächlich erstelle ich eine Hashtabelle zum Speichern von Informationen zur Schreibzeit von Dateien. Wenn die Hashtabelle den geänderten Dateipfad hat und der Zeitwert mit der Änderung der aktuell benachrichtigten Datei übereinstimmt, weiß ich, dass es sich um das Duplikat des Ereignisses handelt, und ignoriere es.
quelle
ToString("o")
aber auf weitere Fehler vorbereitet sein.Versuchen Sie es mit diesem Code:
quelle
if (let==false) { ... } else { let = false; }
? Unglaublich, wie dies zu positiven Stimmen kam, dies muss nur eine Frage der StackOverflow-Abzeichen sein.Hier ist mein Ansatz:
Dies ist die Lösung, mit der ich dieses Problem in einem Projekt gelöst habe, in dem ich die Datei als Anhang in einer E-Mail gesendet habe. Es wird das zweimal ausgelöste Ereignis auch mit einem kleineren Zeitintervall leicht vermeiden, aber in meinem Fall war 1000 in Ordnung, da ich mit wenigen fehlenden Änderungen zufriedener war als mit dem Überfluten des Postfachs mit> 1 Nachricht pro Sekunde. Zumindest funktioniert es einwandfrei, wenn mehrere Dateien genau gleichzeitig geändert werden.
Eine andere Lösung, an die ich gedacht habe, wäre, die Liste durch ein Wörterbuch zu ersetzen, das Dateien ihrem jeweiligen MD5 zuordnet, sodass Sie kein beliebiges Intervall auswählen müssen, da Sie den Eintrag nicht löschen, sondern seinen Wert aktualisieren müssen storniere deine Sachen, wenn es sich nicht geändert hat. Es hat den Nachteil, dass ein Wörterbuch im Speicher wächst, wenn Dateien überwacht werden und immer mehr Speicher verbraucht, aber ich habe irgendwo gelesen, dass die Anzahl der überwachten Dateien vom internen Puffer des FSW abhängt, also vielleicht nicht so kritisch. Keine Ahnung, wie sich die MD5-Rechenzeit auch auf die Leistung Ihres Codes auswirken würde, vorsichtig = \
quelle
lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
_changedFiles
Zugriff erfolgt über mehrere Threads. Eine Möglichkeit, dies zu beheben, ist die Verwendung von aConcurrentDictionary
anstelle vonList
. Eine andere Möglichkeit besteht darin, den Strom sowohlForm
derTimer.SynchronizingObject
Eigenschaft als auch derFileSystemWatcher.SynchronizingObject
Eigenschaft zuzuweisen .Ich habe ein Git-Repo mit einer Klasse erstellt, die erweitert wird
FileSystemWatcher
, um die Ereignisse nur dann auszulösen, wenn der Kopiervorgang abgeschlossen ist. Es werden alle geänderten Ereignisse außer dem letzten verworfen und erst ausgelöst, wenn die Datei zum Lesen verfügbar ist.Laden Sie FileSystemSafeWatcher herunter und fügen Sie es Ihrem Projekt hinzu.
Verwenden Sie es dann wie gewohnt
FileSystemWatcher
und überwachen Sie, wann die Ereignisse ausgelöst werden.quelle
Ich weiß, dass dies ein altes Problem ist, hatte aber das gleiche Problem und keine der oben genannten Lösungen hat wirklich den Trick für das Problem getan, mit dem ich konfrontiert war. Ich habe ein Wörterbuch erstellt, das den Dateinamen mit LastWriteTime abbildet. Wenn sich die Datei also nicht im Wörterbuch befindet, wird der Vorgang auf andere Weise fortgesetzt. Überprüfen Sie, wann die letzte geänderte Zeit war, und führen Sie den Code aus, wenn sie sich von der im Wörterbuch enthaltenen unterscheidet.
quelle
your code here
Abschnitt sollten Sie das dateTimeDictionary hinzufügen oder aktualisieren.dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
Ein möglicher "Hack" wäre, die Ereignisse mithilfe von Reactive Extensions zu drosseln, zum Beispiel:
In diesem Fall drossle ich auf 50 ms, auf meinem System war das genug, aber höhere Werte sollten sicherer sein. (Und wie gesagt, es ist immer noch ein "Hack").
quelle
.Distinct(e => e.FullPath)
was ich viel intuitiver finde, um damit umzugehen. Und Sie haben das Verhalten wiederhergestellt, das von der API erwartet wird.Ich habe hier eine sehr schnelle und einfache Problemumgehung, die für mich funktioniert, und egal, ob das Ereignis gelegentlich ein- oder zweimal oder mehrmals ausgelöst wird, probieren Sie es aus:
quelle
Hier ist eine neue Lösung, die Sie ausprobieren können. Funktioniert gut für mich. Entfernen Sie im Ereignishandler für das geänderte Ereignis den Handler programmgesteuert aus der Designerausgabe, falls gewünscht, und fügen Sie den Handler programmgesteuert wieder hinzu. Beispiel:
quelle
this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;
sollte das Richtige tun.Der Hauptgrund war, dass die letzte Zugriffszeit des ersten Ereignisses die aktuelle Zeit war (Schreibzeit der Datei oder geänderte Zeit). Das zweite Ereignis war die ursprüngliche letzte Zugriffszeit der Datei. Ich löse unter Code.
quelle
Ich habe viel Zeit mit dem FileSystemWatcher verbracht, und einige der hier beschriebenen Ansätze funktionieren nicht. Der Ansatz zum Deaktivieren von Ereignissen hat mir sehr gut gefallen, aber leider funktioniert er nicht, wenn> 1 Datei gelöscht wird. Die zweite Datei wird am meisten, wenn nicht sogar immer, übersehen. Also benutze ich den folgenden Ansatz:
quelle
Dieser Code hat bei mir funktioniert.
quelle
meistens für die Zukunft mich :)
Ich habe einen Wrapper mit Rx geschrieben:
Verwendungszweck:
quelle
Ich habe die Art und Weise, wie ich Dateien in Verzeichnissen überwache, geändert. Anstatt den FileSystemWatcher zu verwenden, frage ich Speicherorte in einem anderen Thread ab und schaue dann auf die LastWriteTime der Datei.
Anhand dieser Informationen und unter Beibehaltung eines Index eines Dateipfads und der letzten Schreibzeit kann ich Dateien ermitteln, die geändert wurden oder an einem bestimmten Speicherort erstellt wurden. Dies entfernt mich von den Kuriositäten des FileSystemWatcher. Der Hauptnachteil ist, dass Sie eine Datenstruktur benötigen, um LastWriteTime und den Verweis auf die Datei zu speichern, diese jedoch zuverlässig und einfach zu implementieren ist.
quelle
Sie könnten versuchen, es zum Schreiben zu öffnen, und wenn dies erfolgreich ist, können Sie davon ausgehen, dass die andere Anwendung mit der Datei fertig ist.
Wenn Sie es nur zum Schreiben öffnen, wird das geänderte Ereignis nicht ausgelöst. Es sollte also sicher sein.
quelle
quelle
Entschuldigung für die Grabgrabung, aber ich habe dieses Problem jetzt schon eine Weile bekämpft und endlich einen Weg gefunden, um mit diesen mehrfach abgefeuerten Ereignissen umzugehen. Ich möchte mich bei allen in diesem Thread bedanken, da ich ihn bei der Bekämpfung dieses Problems in vielen Referenzen verwendet habe.
Hier ist mein vollständiger Code. Es verwendet ein Wörterbuch, um das Datum und die Uhrzeit des letzten Schreibvorgangs der Datei zu verfolgen. Dieser Wert wird verglichen, und wenn er identisch ist, werden die Ereignisse unterdrückt. Anschließend wird der Wert nach dem Starten des neuen Threads festgelegt.
quelle
Ereignis, wenn nicht gefragt, ist es eine Schande, dass es keine fertigen Lösungsbeispiele für F # gibt. Um dies hier zu beheben, ist mein Rezept, nur weil ich kann und F # eine wunderbare .NET-Sprache ist.
Doppelte Ereignisse werden mithilfe des
FSharp.Control.Reactive
Pakets herausgefiltert, das nur ein F # -Wrapper für reaktive Erweiterungen ist. All dies kann auf einen vollständigen Rahmen ausgerichtet sein odernetstandard2.0
:quelle
In meinem Fall muss die letzte Zeile einer Textdatei abgerufen werden, die von einer anderen Anwendung eingefügt wird, sobald das Einfügen abgeschlossen ist. Hier ist meine Lösung. Wenn das erste Ereignis ausgelöst wird, deaktiviere ich den Watcher, um andere auszulösen, und rufe dann den Timer TimeElapsedEvent auf, da ich beim Aufruf meiner Handle-Funktion OnChanged die Größe der Textdatei benötige, aber die Größe zu diesem Zeitpunkt nicht die tatsächliche Größe ist. Dies ist die Größe der Datei unmittelbar vor dem Einfügen. Also warte ich eine Weile, um mit der richtigen Dateigröße fortzufahren.
quelle
Versuchen Sie dies, es funktioniert gut
quelle
Ich wollte nur auf das letzte Ereignis reagieren, nur für den Fall, auch bei einer Änderung der Linux-Datei schien es, dass die Datei beim ersten Aufruf leer war und beim nächsten erneut gefüllt wurde, und es machte mir nichts aus, einige Zeit zu verlieren, nur für den Fall, dass das Betriebssystem beschlossen, einige Datei- / Attributänderungen vorzunehmen.
Ich verwende hier .NET Async, um das Threading zu unterstützen.
quelle
Ich denke, die beste Lösung, um das Problem zu lösen, ist die Verwendung reaktiver Erweiterungen. Wenn Sie ein Ereignis in ein beobachtbares Ereignis umwandeln, können Sie einfach Throttling (..) hinzufügen (ursprünglich Debounce (..) genannt).
Beispielcode hier
quelle
Ich konnte dies tun, indem ich eine Funktion hinzufügte, die in einem Pufferarray nach Duplikaten sucht.
Führen Sie dann die Aktion aus, nachdem das Array für X-Zeit nicht mit einem Timer geändert wurde: - Setzen Sie den Timer jedes Mal zurück, wenn etwas in den Puffer geschrieben wird. - Führen Sie die Aktion bei einem Häkchen aus
Dies fängt auch einen anderen Duplikationstyp ab. Wenn Sie eine Datei in einem Ordner ändern, löst der Ordner auch ein Änderungsereignis aus.
quelle
Diese Lösung funktionierte für mich in der Produktionsanwendung:
Umgebung:
VB.Net Framework 4.5.2
Manuelles Festlegen von Objekteigenschaften: NotifyFilter = Size
Verwenden Sie dann diesen Code:
quelle
Versuche dies!
quelle
Ich musste mehrere Ideen aus den obigen Beiträgen kombinieren und eine Dateisperrprüfung hinzufügen, damit es für mich funktioniert:
quelle
Ich näherte mich dem Problem der doppelten Erstellung wie folgt, bei dem das erste Ereignis ignoriert wird:
quelle