L'evento FileSystemWatcher Changed modificato viene generato due volte


336

Ho un'applicazione in cui sto cercando un file di testo e se sono state apportate modifiche al file sto usando il OnChangedgestore eventi per gestire l'evento. Sto usando il NotifyFilters.LastWriteTimema l'evento è ancora due volte sparato. Ecco il codice

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

Nel mio caso, OnChangedviene chiamato due volte, quando cambio il file di testo version.txte lo salvo.


2
@BrettRigby: non c'è da stupirsi. Nessuna di queste potenziali risposte fornisce la soluzione al problema. Sono tutte soluzioni alternative per problemi specifici. In effetti, nessuno di loro ha risolto il mio problema specifico (devo ammetterlo, non li ho testati tutti).

È una soluzione alternativa, ma dovrebbe essere giudicata dalla qualità della soluzione alternativa. Tenere traccia delle modifiche funziona perfettamente ed è semplice. OP sta chiedendo un modo per sopprimere gli eventi duplicati, ed è quello che danno le risposte di seguito. msdn.microsoft.com/en-us/library/… Spiega che i molteplici eventi potrebbero essere causati dall'antivirus o da altre "cose ​​complicate del file system" (che sembrano solo una scusa).
Tyler Montney,

2
Di recente ho risolto questo problema github.com/Microsoft/dotnet/issues/347
Stephan Ahlf

2
Ho creato una classe che ti aiuta a ottenere solo un evento. Puoi ottenere il codice da github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Risposte:


277

Temo che si tratti di un noto bug / funzionalità della FileSystemWatcherclasse. Questo è dalla documentazione della classe:

In alcune situazioni potresti notare che un singolo evento di creazione genera più eventi creati gestiti dal tuo componente. Ad esempio, se si utilizza un componente FileSystemWatcher per monitorare la creazione di nuovi file in una directory e quindi testarlo utilizzando Blocco note per creare un file, è possibile che vengano generati due eventi creati anche se è stato creato un solo file. Questo perché Blocco note esegue più azioni del file system durante il processo di scrittura. Blocco note scrive sul disco in batch che creano il contenuto del file e quindi gli attributi del file. Altre applicazioni possono funzionare allo stesso modo. Poiché FileSystemWatcher monitora le attività del sistema operativo, verranno raccolti tutti gli eventi generati da queste applicazioni.

Ora questo frammento di testo riguarda l' Createdevento, ma la stessa cosa vale anche per altri eventi di file. In alcune applicazioni potresti essere in grado di aggirare questo problema usando ilNotifyFilter proprietà, ma la mia esperienza dice che a volte devi fare anche dei filtri manuali duplicati (hack).

Qualche tempo fa ho prenotato una pagina con alcuni suggerimenti su FileSystemWatcher . Potresti voler dare un'occhiata.



151

Ho "risolto" quel problema usando la seguente strategia nel mio delegato:

// 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
Ci ho provato e ha funzionato se avessi modificato un file alla volta, ma se avessi modificato due file alla volta (come copia 1.txt e 2.txt per copiare 1.txt e copia di 2.txt), si sarebbe solo alzato un evento non due come previsto.
Christopher Painter,

2
Sono passati un paio di mesi, ma penso che alla fine ho fatto in modo che l'evento chiamasse un metodo che inserisce la logica aziendale all'interno di un'istruzione lock. In questo modo, se ricevo eventi extra, fanno la fila fino al loro turno e non c'è niente da fare perché la precedente iterazione si è occupata di tutto.
Christopher Painter,

15
Questo sembra risolvere il problema, ma non lo fa. Se un altro processo sta apportando modifiche che potresti perderle, il motivo per cui sembra funzionare è perché l'IO dell'altro processo è asincrono e disabiliti il ​​monitoraggio fino a quando non hai completato l'elaborazione, creando così una condizione di competizione con altri eventi che potrebbero essere di interesse. Ecco perché @ChristopherPainter ha osservato il suo problema.
Jf Beaulac,

14
-1: cosa succede se si verifica un'altra modifica a cui si sarebbe interessati mentre disabilitato?
G. Stoynev,

2
@cYounes: a meno che tu non faccia le tue cose in modo asincrono.
David Brabant,

107

Eventuali OnChangedeventi duplicati dal FileSystemWatcherpossono essere rilevati ed eliminati controllando il File.GetLastWriteTimetimestamp sul file in questione. Così:

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
Mi piace quella soluzione, ma ho usato Rx per fare la cosa "giusta" (cambia "Rename"il nome dell'evento che ti interessa):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski,

4
Mi sto perdendo qualcosa? Non capisco come funzionerà. Da quanto ho visto gli eventi si attivano contemporaneamente, quindi se entrambi accedono allo stesso evento contemporaneamente, entrambi inizieranno a funzionare prima che sia impostato lastRead.
Peter Jamsmenson,

Come DateTimeha solo millisecondo risoluzione, questo metodo funziona anche se si sostituisce File.GetLastWriteTimecon DateTime.Now. A seconda della situazione, è inoltre possibile utilizzare la a.FullNamevariabile in globale per rilevare eventi duplicati.
Roland,

@PeterJamsmenson Gli eventi non si attivano esattamente contemporaneamente. Ad esempio, Blocco note può generare diversi eventi durante il salvataggio delle modifiche su disco, ma questi eventi vengono generati in sequenza, uno dopo l'altro, durante i vari passaggi che il Blocco note deve eseguire per un salvataggio. Il metodo di Babu funziona alla grande.
Roland

10
Non funziona poiché gli eventi generati sono separati da un segno di spunta: Ultima ora di scrittura: 636076274162565607 Ultima ora di scrittura: 636076274162655722
Asheh,

23

Ecco la mia soluzione che mi ha aiutato a fermare l'evento due volte:

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

Qui ho impostato la NotifyFilterproprietà con solo nome file e dimensioni.
watcherè il mio oggetto di FileSystemWatcher. Spero che questo possa aiutare.


9
Inoltre, in Blocco note, ho creato un file con quattro caratteri: abcd in esso. Ho quindi aperto una nuova istanza di Blocco note e ho inserito gli stessi quattro caratteri. Ho scelto File | Salva con nome e ha scelto lo stesso file. Il file è identico e le dimensioni e il nome del file non cambiano, poiché il file ha le stesse quattro lettere, quindi questo non si attiva.
Rhyous,

30
È possibile che venga apportata una modifica autentica che non altera la dimensione del file, pertanto questa tecnica fallirebbe in quella situazione.
Lee Grissom,

3
Immagino che sia un caso abbastanza comune in cui sai che qualsiasi modifica significativa modificherà la dimensione del file (ad esempio, il mio caso è stato aggiunto a un file di registro). Mentre chiunque usi questa soluzione dovrebbe essere consapevole (e documentare) di tale presupposto, questo era esattamente ciò di cui avevo bisogno.
GrandOpener

1
@GrandOpener: questo non è sempre vero. Nel mio caso sto guardando file in cui il contenuto è costituito da un solo carattere che è 0 o 1.

8

Il mio scenario è che ho una macchina virtuale con un server Linux al suo interno. Sto sviluppando file sull'host Windows. Quando cambio qualcosa in una cartella sull'host, voglio che tutte le modifiche vengano caricate, sincronizzate sul server virtuale tramite Ftp. Questo è il modo in cui elimino l'evento di modifica duplicato quando scrivo su un file (che contrassegna anche la cartella contenente il file da modificare):

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

Principalmente creo un hashtable per archiviare le informazioni sul tempo di scrittura dei file. Quindi se l'hashtable ha il percorso file che viene modificato e il suo valore temporale è lo stesso della modifica del file attualmente notificato, allora so che è il duplicato dell'evento e lo ignoro.


Suppongo che svuoti periodicamente l'hashtable.
ThunderGr

Ciò sarebbe accurato per il secondo, ma se il periodo tra i due cambiamenti è abbastanza lungo da passare un secondo, fallirà. Inoltre, se si desidera una maggiore precisione, è possibile utilizzare ToString("o")ma essere preparati per più guasti.
Pragmateek,

5
Non confrontare le stringhe, usa DateTime.Equals ()
Phillip Kamikaze,

No, non farlo. Non sono uguali. Nel caso del mio attuale progetto, sono distanti circa un millisecondo. Uso (newtime-oldtime) .TotalMilliseconds <(soglia arbitraria, di solito 5ms).
Flynn1179,

8

Prova con questo codice:

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
Questa è la soluzione migliore, usando un semaforo anziché un timer.
Aaron Blenkush,

1
Quale semaforo? Vedo solo una variabile booleana qui. Inoltre, il problema principale non è stato risolto: FileSystemEventHandler sta ancora generando più eventi. E quale efficacia ha questo codice? if (let==false) { ... } else { let = false; }? Incredibile come questo abbia ottenuto voti positivi, questo deve essere solo una questione di badge StackOverflow.
sɐunıɔ ןɐ qɐp

8

Ecco il mio approccio:

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

Questa è la soluzione che ho usato per risolvere questo problema su un progetto in cui stavo inviando il file come allegato in una mail. Eviterà facilmente l'evento due volte attivato anche con un intervallo di timer più piccolo, ma nel mio caso 1000 andava bene dato che ero più felice con alcune modifiche mancanti rispetto all'inondazione della cassetta postale con> 1 messaggio al secondo. Almeno funziona bene nel caso in cui più file vengano cambiati contemporaneamente.

Un'altra soluzione a cui ho pensato sarebbe quella di sostituire l'elenco con un dizionario che mappasse i file nei rispettivi MD5, quindi non dovresti scegliere un intervallo arbitrario poiché non dovresti eliminare la voce ma aggiornarne il valore e annulla la tua roba se non è cambiata. Ha il rovescio della medaglia di avere un dizionario che cresce in memoria mentre i file vengono monitorati e consumano sempre più memoria, ma ho letto da qualche parte che la quantità di file monitorati dipende dal buffer interno del FSW, quindi forse non è così critico. Non so come il tempo di elaborazione MD5 influenzerebbe le prestazioni del tuo codice, attento = \


La tua soluzione funziona benissimo per me. Solo, hai dimenticato di aggiungere il file all'elenco _changedFiles. La prima parte del codice dovrebbe apparire così:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey,

Ho annullato la votazione di 4 risposte sopra e ho votato a favore di questa. La tua risposta è la prima a fare quello che dovrebbe fare prendendo l'evento LAST e non la prima. Come spiegato da @Jorn, il problema è che i file vengono scritti in batch. Altre soluzioni non hanno funzionato per me.
CodingYourLife

La tua soluzione non è thread-safe. Si _changedFilesaccede da più thread. Un modo per risolverlo è usare un ConcurrentDictionaryinvece di List. Un altro modo è quello di assegnare la corrente Formalla Timer.SynchronizingObjectproprietà, così come alla FileSystemWatcher.SynchronizingObjectproprietà.
Theodor Zoulias,

5

Ho creato un repository Git con una classe che si estende FileSystemWatcher per attivare gli eventi solo al termine della copia. Elimina tutti gli eventi modificati tranne l'ultimo e lo solleva solo quando il file diventa disponibile per la lettura.

Scarica FileSystemSafeWatcher e aggiungilo al tuo progetto.

Quindi usalo come un normale FileSystemWatchere monitora quando vengono attivati ​​gli eventi.

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

Questo sembra non riuscire quando viene generato un evento in una directory. Ho funzionato avvolgendo un controllo di directory prima di aprire il file
Sam

Nonostante il refuso nell'esempio, questa sembra essere una soluzione praticabile per me. Tuttavia, nel mio caso ci possono essere una dozzina di aggiornamenti in un secondo, quindi ho dovuto ridurre drasticamente _consolidationInterval per non perdere nessuna modifica. Mentre 10 ms sembrano andare bene, perdo comunque circa il 50% degli aggiornamenti se imposto _consolidationInterval su 50 ms. Devo ancora eseguire alcuni test per trovare il valore più adatto.

_consolidationInterval sembra funzionare bene per me. Vorrei che qualcuno lo rovinasse e lo trasformasse in un pacchetto NuGet.
zumalifeguard,

1
Grazie :) Ha risolto il mio problema. Spero che gli eventi creati e copiati funzionino correttamente con un singolo osservatore per risolvere bene questo problema. stackoverflow.com/questions/55015132/…
techno

1
Questo è eccellente L'ho implementato nel mio progetto ed è stato battuto ogni tentativo che ho fatto nel tentativo di romperlo. Grazie.
Christh,

4

So che questo è un vecchio problema, ma ha avuto lo stesso problema e nessuna delle soluzioni di cui sopra ha davvero risolto il problema che stavo affrontando. Ho creato un dizionario che mappa il nome del file con LastWriteTime. Quindi, se il file non è presente nel dizionario, si procederà con il processo, altri saggi controlli per vedere quando è stata l'ultima volta modificata e se è diverso da quello che è nel dizionario eseguire il codice.

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

Questa è una soluzione solida, ma manca una riga di codice. nella your code heresezione, è necessario aggiungere o aggiornare dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake,

Non ha funzionato per me. Il gestore delle modifiche viene chiamato due volte e il file ha un timestamp diverso la seconda volta. Potrebbe essere perché è un file di grandi dimensioni e la scrittura era in corso la prima volta. Ho trovato un timer per comprimere eventi duplicati ha funzionato meglio.
michael,

3

Un possibile 'hack' sarebbe quello di limitare gli eventi usando ad esempio le estensioni reattive:

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

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

watcher.EnableRaisingEvents = true;

In questo caso sto limitando a 50ms, sul mio sistema era abbastanza, ma valori più alti dovrebbero essere più sicuri. (E come ho detto, è ancora un 'hack').


Ho usato quello .Distinct(e => e.FullPath)che trovo molto più intuitivo da affrontare. E hai ripristinato il comportamento che ci si aspetterebbe dall'API.
Kjellski,

3

Ho una soluzione molto rapida e semplice qui, funziona per me, e non importa che l'evento venga attivato una o due o più volte di tanto in tanto, controlla:

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

All'inizio ho pensato che avrebbe funzionato per me, ma non lo è. Ho una situazione in cui il contenuto dei file viene talvolta sovrascritto e altre volte il file viene eliminato e ricreato. Mentre la tua soluzione sembra funzionare nel caso in cui il file venga sovrascritto, non funziona sempre nel caso in cui il file venga ricreato. In quest'ultimo caso gli eventi a volte si perdono.

Prova a ordinare diversi tipi di eventi e gestirli separatamente, offro solo una possibile soluzione. in bocca al lupo.
Xiaoyuvax,

sebbene non lo provi, non sono del tutto sicuro che questo non funzioni per la creazione e la cancellazione. dovrebbe anche essere teoricamente applicabile. Poiché l'istruzione fireCount ++ e if () sono entrambi atomici e non saranno messi in attesa. anche con due eventi innescati in competizione tra loro. Immagino che ci debba essere qualcos'altro che causa il tuo problema. (per perso? cosa intendi?)
Xiaoyuvax,

3

Ecco una nuova soluzione che puoi provare. Funziona bene per me. Nel gestore eventi per l'evento modificato rimuovere a livello di codice il gestore dall'output del designer, se desiderato, quindi aggiungere nuovamente il gestore a livello di codice. esempio:

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
Non è necessario invocare il costruttore del tipo delegato. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;dovrebbe fare la cosa giusta.
bartonjs,

@bartonjs Grazie per quello. Non sono sicuro del motivo per cui ho invocato l'intero costruttore. Onestamente è molto probabilmente un errore da principiante. Indipendentemente da ciò, sembra che il mio trucco di una correzione abbia funzionato abbastanza bene.
Fancy_Mammoth,

2

Il motivo principale era che l'ora dell'ultimo accesso del primo evento era l'ora corrente (scrittura del file o ora modificata). quindi il secondo evento era l'ora dell'ultimo accesso originale del file. Risolvo sotto il codice.

        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;


2

Ho trascorso una notevole quantità di tempo usando FileSystemWatcher e alcuni degli approcci qui non funzioneranno. Mi è piaciuto molto l'approccio degli eventi disabilitanti, ma sfortunatamente, non funziona se si elimina> 1 file, il secondo file mancherà di più se non tutte le volte. Quindi uso il seguente approccio:

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

Questo codice ha funzionato per me.

        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

principalmente per me futuro :)

Ho scritto un wrapper usando 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();
    }
}

Uso:

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

Ho cambiato il modo in cui controllo i file nelle directory. Invece di utilizzare FileSystemWatcher, eseguo il polling delle posizioni su un altro thread e quindi guardo LastWriteTime del file.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Usando queste informazioni e mantenendo un indice di un percorso di file e dell'ora di scrittura più recente, posso determinare i file che sono stati modificati o che sono stati creati in una posizione particolare. Questo mi rimuove dalle stranezze di FileSystemWatcher. Lo svantaggio principale è che è necessaria una struttura di dati per archiviare LastWriteTime e il riferimento al file, ma è affidabile e facile da implementare.


9
oltre a dover masterizzare cicli in background invece di essere avvisato da un evento di sistema.
Matthew Whited,

1

Potresti provare ad aprirlo per la scrittura e, in caso di successo, potresti supporre che l'altra applicazione sia stata eseguita con il file.

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

Basta aprirlo per scrivere sembra non sollevare l'evento modificato. Quindi dovrebbe essere sicuro.


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
Sebbene questa possa essere la migliore soluzione alla domanda posta, è sempre bello aggiungere alcuni commenti sul perché hai scelto questo approccio e perché pensi che funzioni. :)
waka,

1

Ci scusiamo per il grave scavo, ma sto combattendo questo problema da un po 'di tempo e finalmente ho trovato un modo per gestire questi eventi multipli licenziati. Vorrei ringraziare tutti in questa discussione poiché l'ho usata in molti riferimenti quando ho affrontato questo problema.

Ecco il mio codice completo. Utilizza un dizionario per tenere traccia della data e dell'ora dell'ultima scrittura del file. Confronta quel valore e, se è lo stesso, sopprime gli eventi. Quindi imposta il valore dopo aver avviato il nuovo thread.

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

Ho usato questo in uno dei miei progetti. Funziona alla grande!
Tyler Montney,

Non funziona poiché gli eventi generati sono separati da un segno di spunta: Ultima ora di scrittura: 636076274162565607 Ultima ora di scrittura: 636076274162655722
Professore di programmazione

1

Se l'evento non viene richiesto, è un peccato che non ci siano campioni di soluzione pronti per F #. Per risolvere questo problema ecco la mia ricetta, solo perché posso e F # è un meraviglioso linguaggio .NET.

Gli eventi duplicati vengono filtrati usando il FSharp.Control.Reactivepacchetto, che è solo un wrapper F # per estensioni reattive. Tutto ciò che può essere mirato al framework completo o 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

Nel mio caso è necessario ottenere l'ultima riga di un file di testo che viene inserito da un'altra applicazione, non appena viene effettuato l'inserimento. Ecco la mia soluzione Quando viene generato il primo evento, disabilito il watcher dal sollevare altri, quindi chiamo il timer TimeElapsedEvent perché quando viene chiamata la mia funzione handle OnChanged ho bisogno della dimensione del file di testo, ma la dimensione in quel momento non è la dimensione effettiva, è la dimensione del file immediatamente prima dell'inserimento. Quindi aspetto un po 'per procedere con la giusta dimensione del file.

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

Prova questo, funziona bene

  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

Volevo reagire solo sull'ultimo evento, per ogni evenienza, anche su una modifica del file Linux sembrava che il file fosse vuoto alla prima chiamata e poi riempito di nuovo alla successiva e non mi dispiace perdere un po 'di tempo nel caso in cui il sistema operativo ha deciso di apportare alcune modifiche al file / attributo.

Sto usando .NET async qui per aiutarmi a fare il threading.

    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

Penso che la soluzione migliore per risolvere il problema sia usare le estensioni reattive Quando trasformi un evento in osservabile, puoi semplicemente aggiungere Throttling (..) (originariamente chiamato Debounce (..))

Codice di esempio qui

        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

Sono stato in grado di farlo aggiungendo una funzione che controlla i duplicati in un array di buffer.

Quindi eseguire l'azione dopo che l'array non è stato modificato per X volte usando un timer: - Reimposta il timer ogni volta che qualcosa viene scritto nel buffer - Esegui l'azione su tick

Questo cattura anche un altro tipo di duplicazione. Se si modifica un file all'interno di una cartella, la cartella genera anche un evento Modifica.

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

Questa soluzione ha funzionato per me sull'applicazione di produzione:

Ambiente:

VB.Net Framework 4.5.2

Imposta manualmente le proprietà dell'oggetto: NotifyFilter = Size

Quindi utilizzare questo codice:

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

Usa lo stesso concetto di @Jamie Krcmar ma per VB.NET
wpcoder

0

Prova questo!

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

Ho dovuto combinare diverse idee dai post sopra e aggiungere il controllo del blocco dei file per farlo funzionare per me:

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

Ho affrontato il problema della doppia creazione come questa, che ignora il primo evento:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.