FileSystemWatcher Geändertes Ereignis wird zweimal ausgelöst


334

Ich habe eine Anwendung, in der ich nach einer Textdatei suche. Wenn Änderungen an der Datei vorgenommen werden, verwende ich den OnChangedEventhandler, um das Ereignis zu behandeln. Ich benutze das, NotifyFilters.LastWriteTimeaber 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 OnChangedwird das zweimal aufgerufen, wenn ich die Textdatei ändere version.txtund speichere.


2
@ BrettRigby: Kein Wunder. Keine dieser möglichen Antworten bietet die Lösung für das Problem. Sie sind alle Problemumgehungen für bestimmte Probleme. Tatsächlich hat keiner von ihnen mein spezifisches Problem gelöst (ich muss zugeben, ich habe nicht alle getestet).

Es ist eine Problemumgehung, die jedoch an der Qualität der Problemumgehung gemessen werden sollte. Das Verfolgen der Änderungen funktioniert perfekt und ist einfach. OP fragt nach einer Möglichkeit, doppelte Ereignisse zu unterdrücken, und genau das geben die folgenden Antworten. msdn.microsoft.com/en-us/library/… Erklärt, dass die mehreren Ereignisse durch Antivirenprogramme oder andere "komplizierte Dateisystem-Dinge" verursacht werden können (was nur nach einer Ausrede klingt).
Tyler Montney

2
Ich habe kürzlich diese Ausgabe geöffnet github.com/Microsoft/dotnet/issues/347
Stephan Ahlf

2
Ich habe eine Klasse erstellt, mit der Sie nur ein Ereignis erhalten. Sie können den Code von github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Antworten:


275

Ich befürchte, dass dies ein bekannter Fehler / eine bekannte Funktion der FileSystemWatcherKlasse ist. Dies ist aus der Dokumentation der Klasse:

In bestimmten Situationen stellen Sie möglicherweise fest, dass ein einzelnes Erstellungsereignis mehrere erstellte Ereignisse generiert, die von Ihrer Komponente verarbeitet werden. Wenn Sie beispielsweise eine FileSystemWatcher-Komponente verwenden, um die Erstellung neuer Dateien in einem Verzeichnis zu überwachen, und diese dann mit Notepad zum Erstellen einer Datei testen, werden möglicherweise zwei erstellte Ereignisse generiert, obwohl nur eine einzige Datei erstellt wurde. Dies liegt daran, dass Notepad während des Schreibvorgangs mehrere Dateisystemaktionen ausführt. Notepad schreibt in Stapeln auf die Festplatte, die den Inhalt der Datei und dann die Dateiattribute erstellen. Andere Anwendungen können auf die gleiche Weise ausgeführt werden. Da FileSystemWatcher die Aktivitäten des Betriebssystems überwacht, werden alle Ereignisse, die diese Anwendungen auslösen, erfasst.

In diesem Text geht es um das CreatedEreignis, 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.


6
Raymond Chen hat gerade darüber gebloggt
Cody Gray

150

Ich habe dieses Problem mit der folgenden Strategie in meinem Delegaten "behoben":

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

14
Ich habe das versucht und es hat funktioniert, wenn ich jeweils eine Datei geändert habe, aber wenn ich zwei Dateien gleichzeitig geändert habe (wie Kopie 1.txt und 2.txt, um 1.txt zu kopieren und 2.txt zu kopieren), wird es nur ausgelöst ein Ereignis nicht zwei wie erwartet.
Christopher Painter

2
Es ist ein paar Monate her, aber ich denke, am Ende hat das Ereignis eine Methode aufgerufen, die Geschäftslogik in eine Lock-Anweisung einfügt. Auf diese Weise stehen sie an, wenn ich zusätzliche Ereignisse erhalte, bis sie an der Reihe sind, und sie haben nichts zu tun, da sich die vorherige Iteration um alles gekümmert hat.
Christopher Painter

15
Dies scheint das Problem zu beheben, tut es aber nicht. Wenn ein anderer Prozess Änderungen vornimmt, können Sie diese verlieren. Der Grund dafür scheint zu sein, dass die E / A des anderen Prozesses asynchron ist und Sie die Überwachung deaktivieren, bis Sie Ihre Verarbeitung abgeschlossen haben, wodurch eine Race-Bedingung mit anderen Ereignissen erstellt wird von Interesse. Deshalb hat @ChristopherPainter sein Problem beobachtet.
Jf Beaulac

14
-1: Was passiert, wenn eine andere Änderung, an der Sie interessiert sind, deaktiviert wird?
G. Stoynev

2
@cYounes: es sei denn, du machst deine Sachen asynchron.
David Brabant

107

Alle doppelten OnChangedEreignisse aus dem FileSystemWatcherkönnen erkannt und verworfen werden, indem der File.GetLastWriteTimeZeitstempel in der betreffenden Datei überprüft wird . Wie so:

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
}

13
Ich mag diese Lösung, aber ich habe Rx verwendet, um das "Richtige" zu tun (ändern Sie "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);
Kjellski

4
Vermisse ich etwas Ich verstehe nicht, wie das funktionieren wird. Nach dem, was ich gesehen habe, werden die Ereignisse gleichzeitig ausgelöst. Wenn beide gleichzeitig in das obige Ereignis eintreten, werden beide ausgeführt, bevor lastRead festgelegt ist.
Peter Jamsmenson

Da DateTimenur Millisekundenauflösung hat, funktioniert diese Methode auch wenn Sie ersetzen File.GetLastWriteTimemit DateTime.Now. Abhängig von Ihrer Situation können Sie auch die a.FullNamein einer globalen Variablen verwenden, um doppelte Ereignisse zu erkennen.
Roland

@PeterJamsmenson Die Ereignisse werden nicht genau gleichzeitig ausgelöst. Beispielsweise kann Notepad beim Speichern von Änderungen auf der Festplatte mehrere Ereignisse generieren. Diese Ereignisse werden jedoch nacheinander während der verschiedenen Schritte ausgelöst, die Notepad zum Speichern ausführen muss. Babus Methode funktioniert großartig.
Roland

10
Funktioniert nicht, da die ausgelösten Ereignisse Ticks voneinander entfernt sind: Letzte Schreibzeit: 636076274162565607 Letzte Schreibzeit: 636076274162655722
Asheh

23

Hier ist meine Lösung, die mir geholfen hat, zu verhindern, dass das Ereignis zweimal ausgelöst wird:

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

Hier habe ich die NotifyFilterEigenschaft nur mit Dateiname und Größe festgelegt.
watcherist mein Objekt von FileSystemWatcher. Hoffe das wird helfen.


9
Außerdem habe ich im Editor eine Datei mit vier Zeichen erstellt: abcd. Ich habe dann eine neue Instanz von Notepad geöffnet und die gleichen vier Zeichen eingegeben. Ich habe Datei | gewählt Speichern unter und wählte dieselbe Datei. Die Datei ist identisch und die Größe und der Dateiname ändern sich nicht, da die Datei die gleichen vier Buchstaben hat, sodass dies nicht ausgelöst wird.
Rhyous

30
Es ist möglich, dass eine echte Änderung vorgenommen wird, die die Größe der Datei nicht ändert. Daher würde diese Technik in dieser Situation fehlschlagen.
Lee Grissom

3
Ich würde vermuten, dass es ein ziemlich häufiger Fall ist, in dem Sie wissen, dass jede sinnvolle Änderung die Dateigröße ändert (zum Beispiel wurde mein Fall an eine Protokolldatei angehängt). Während jeder, der diese Lösung verwendet, diese Annahme kennen (und dokumentieren) sollte, war dies genau das, was ich brauchte.
GrandOpener

1
@GrandOpener: Dies ist nicht immer wahr. In meinem Fall schaue ich mir Dateien an, deren Inhalt nur aus einem Zeichen besteht, das entweder 0 oder 1 ist.

8

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):

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

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.


Ich gehe davon aus, dass Sie die Hashtabelle regelmäßig leeren.
ThunderGr

Dies wäre sekundengenau, aber wenn der Zeitraum zwischen den beiden Änderungen lang genug ist, um eine Sekunde zu vergehen, schlägt dies fehl. Wenn Sie mehr Genauigkeit wünschen, können Sie diese auch verwenden, ToString("o")aber auf weitere Fehler vorbereitet sein.
Pragmateek

5
Vergleichen Sie keine Zeichenfolgen, verwenden Sie DateTime.Equals ()
Phillip Kamikaze

Nein, nicht. Sie sind nicht gleich. Bei meinem aktuellen Projekt liegen sie ungefähr eine Millisekunde auseinander. Ich benutze (newtime-oldtime) .TotalMilliseconds <(beliebiger Schwellenwert, normalerweise 5 ms).
Flynn1179

8

Versuchen Sie es mit diesem Code:

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

1
Dies ist die beste Lösung, wenn Sie ein Semaphor anstelle eines Timers verwenden.
Aaron Blenkush

1
Welches Semaphor? Ich sehe hier nur eine boolesche Variable. Darüber hinaus ist das Hauptproblem nicht gelöst: FileSystemEventHandler löst immer noch mehrere Ereignisse aus. Und wie effektiv ist dieser Code? if (let==false) { ... } else { let = false; }? Unglaublich, wie dies zu positiven Stimmen kam, dies muss nur eine Frage der StackOverflow-Abzeichen sein.
sɐunıɔ ןɐ qɐp

8

Hier ist mein Ansatz:

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

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 = \


Ihre Lösung funktioniert gut für mich. Nur haben Sie vergessen, die Datei zur _changedFiles-Liste hinzuzufügen. Der erste Teil des Codes sollte so aussehen:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
Davidthegrey

Ich habe oben 4 Antworten herabgestimmt und diese hochgestimmt. Ihre Antwort ist die erste, die das tut, was sie tun sollte, indem sie das LETZTE Ereignis nimmt, und nicht die erste. Wie von @Jorn erklärt, besteht das Problem darin, dass Dateien stapelweise geschrieben werden. Andere Lösungen haben bei mir nicht funktioniert.
CodingYourLife

Ihre Lösung ist nicht threadsicher. Der _changedFilesZugriff erfolgt über mehrere Threads. Eine Möglichkeit, dies zu beheben, ist die Verwendung von a ConcurrentDictionaryanstelle von List. Eine andere Möglichkeit besteht darin, den Strom sowohl Formder Timer.SynchronizingObjectEigenschaft als auch der FileSystemWatcher.SynchronizingObjectEigenschaft zuzuweisen .
Theodor Zoulias

5

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 FileSystemWatcherund überwachen Sie, wann die Ereignisse ausgelöst werden.

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

Dies scheint fehlzuschlagen, wenn ein Ereignis in einem Verzeichnis ausgelöst wird. Ich habe es zum Laufen gebracht, indem ich vor dem Öffnen der Datei eine Verzeichnisprüfung abgeschlossen habe
Sam

Trotz des Tippfehlers im Beispiel scheint dies für mich eine praktikable Lösung zu sein. In meinem Fall kann es jedoch innerhalb einer Sekunde zu einem Dutzend Updates kommen, sodass ich _consolidationInterval drastisch senken musste, um keine Änderungen zu verpassen. Während 10 ms in Ordnung zu sein scheinen, verliere ich immer noch ungefähr 50% der Updates, wenn ich _consolidationInterval auf 50 ms setze. Ich muss noch einige Tests durchführen, um den Wert zu finden, der am besten passt.

_consolidationInterval scheint gut für mich zu funktionieren. Ich möchte, dass jemand dies teilt und daraus ein NuGet-Paket macht.
zumalifeguard

1
Danke :) Es hat mein Problem gelöst. Ich hoffe, die erstellten und kopierten Ereignisse funktionieren ordnungsgemäß mit einem einzigen Beobachter, um dieses Problem gut zu lösen. stackoverflow.com/questions/55015132/…
Techno

1
Das ist ausgezeichnet. Ich habe es in mein Projekt implementiert und es hat jeden Versuch geschlagen, es zu brechen. Vielen Dank.
Christh

4

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.

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

Dies ist eine solide Lösung, aber es fehlt eine Codezeile. In diesem your code hereAbschnitt sollten Sie das dateTimeDictionary hinzufügen oder aktualisieren. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

Hat bei mir nicht funktioniert. Mein Änderungshandler wird zweimal aufgerufen und die Datei hat beim zweiten Mal einen anderen Zeitstempel. Könnte daran liegen, dass es sich um eine große Datei handelt und der Schreibvorgang zum ersten Mal ausgeführt wurde. Ich fand, dass ein Timer zum Reduzieren doppelter Ereignisse besser funktionierte.
Michael

3

Ein möglicher "Hack" wäre, die Ereignisse mithilfe von Reactive Extensions zu drosseln, zum Beispiel:

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

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

watcher.EnableRaisingEvents = true;

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").


Ich habe verwendet, .Distinct(e => e.FullPath)was ich viel intuitiver finde, um damit umzugehen. Und Sie haben das Verhalten wiederhergestellt, das von der API erwartet wird.
Kjellski

3

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:

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

Zuerst dachte ich, das würde bei mir funktionieren, aber das tut es nicht. Ich habe eine Situation, in der der Dateiinhalt manchmal nur überschrieben wird und manchmal die Datei gelöscht und neu erstellt wird. Während Ihre Lösung zu funktionieren scheint, wenn die Datei überschrieben wird, funktioniert sie nicht immer, wenn die Datei neu erstellt wird. Im letzteren Fall gehen manchmal Ereignisse verloren.

Versuchen Sie, verschiedene Arten von Ereignissen zu sortieren und separat zu behandeln. Ich biete nur eine mögliche Problemumgehung an. Viel Glück.
Xiaoyuvax

Obwohl ich es nicht teste, bin ich mir nicht sicher, ob dies beim Erstellen und Löschen nicht funktioniert. Es sollte auch theoretisch anwendbar sein. Da die Anweisungen fireCount ++ und if () beide atomar sind und nicht warten müssen. selbst bei zwei ausgelösten Ereignissen, die miteinander konkurrieren. Ich denke, es muss noch etwas geben, das deine Probleme verursacht. (Mit verloren? Was meinst du?)
Xiaoyuvax

3

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:

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

1
Sie müssen den Konstruktor des Delegatentyps nicht aufrufen. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;sollte das Richtige tun.
Bartonjs

@bartonjs Danke dafür. Ich bin nicht sicher, warum ich den gesamten Konstruktor aufgerufen habe. Ehrlich gesagt ist es höchstwahrscheinlich ein Anfängerfehler. Unabhängig davon, wie es scheint, hat mein Hack eines Fixes ziemlich gut funktioniert.
Fancy_Mammoth

2

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.

        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;

das gleiche wie diese Antwort
Jean-Paul

2

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:

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

2

Dieser Code hat bei mir funktioniert.

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

2

meistens für die Zukunft mich :)

Ich habe einen Wrapper mit Rx geschrieben:

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

Verwendungszweck:

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)

1

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.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

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.


9
Außerdem müssen Sie Hintergrundzyklen brennen, anstatt von einem Systemereignis benachrichtigt zu werden.
Matthew Whited

1

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.

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

Wenn Sie es nur zum Schreiben öffnen, wird das geänderte Ereignis nicht ausgelöst. Es sollte also sicher sein.


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

1
Dies ist zwar die beste Lösung für die gestellte Frage, aber es ist immer schön, einige Kommentare hinzuzufügen, warum Sie diesen Ansatz gewählt haben und warum Sie glauben, dass er funktioniert. :)
Waka

1

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.

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

Verwendet dies in einem meiner Projekte. Funktioniert super!
Tyler Montney

Funktioniert nicht, da die ausgelösten Ereignisse nur Ticks voneinander entfernt sind: Letzte Schreibzeit: 636076274162565607 Letzte Schreibzeit: 636076274162655722
Professor für Programmierung

1

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.ReactivePakets herausgefiltert, das nur ein F # -Wrapper für reaktive Erweiterungen ist. All dies kann auf einen vollständigen Rahmen ausgerichtet sein oder 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

1

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.

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

1

Versuchen Sie dies, es funktioniert gut

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

1

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.

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

1

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

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

0

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.

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

0

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:

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

Es verwendet das gleiche Konzept wie @Jamie Krcmar, aber für VB.NET
wpcoder

0

Versuche dies!

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

}

0

Ich musste mehrere Ideen aus den obigen Beiträgen kombinieren und eine Dateisperrprüfung hinzufügen, damit es für mich funktioniert:

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

0

Ich näherte mich dem Problem der doppelten Erstellung wie folgt, bei dem das erste Ereignis ignoriert wird:

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
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.