Zdarzenie FileSystemWatcher Changed jest wywoływane dwukrotnie

335

Mam aplikację, w której szukam pliku tekstowego i jeśli w pliku są jakieś zmiany, korzystam z modułu OnChangedobsługi zdarzeń do obsługi zdarzenia. Korzystam z, NotifyFilters.LastWriteTimeale zdarzenie jest uruchamiane dwukrotnie. Oto kod.

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

W moim przypadku OnChangedwywoływany jest dwukrotnie, kiedy zmieniam plik tekstowy version.txti zapisuję go.

użytkownik214707
źródło
2
@BrettRigby: Nic dziwnego. Żadna z tych potencjalnych odpowiedzi nie stanowi rozwiązania problemu. Wszystkie są obejściami określonych problemów. W rzeczywistości żaden z nich nie rozwiązał mojego konkretnego problemu (muszę przyznać, że nie przetestowałem wszystkich z nich).
Jest to obejście, ale należy ocenić na podstawie jakości tego obejścia. Śledzenie zmian działa idealnie i jest proste. OP prosi o sposób na tłumienie zduplikowanych zdarzeń, i to właśnie dają poniższe odpowiedzi. msdn.microsoft.com/en-us/library/… Wyjaśnia, że ​​wiele zdarzeń może być spowodowanych przez program antywirusowy lub inne „skomplikowane rzeczy związane z systemem plików” (co tylko brzmi jak wymówka).
Tyler Montney,
2
Ostatnio zrezygnowałem z tego problemu github.com/Microsoft/dotnet/issues/347
Stephan Ahlf
2
Stworzyłem klasę, która pomaga uzyskać tylko jedno wydarzenie. Możesz pobrać kod z github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Odpowiedzi:

276

Obawiam się, że jest to dobrze znany błąd / funkcja FileSystemWatcherklasy. To jest z dokumentacji klasy:

W niektórych sytuacjach możesz zauważyć, że jedno zdarzenie tworzenia generuje wiele zdarzeń utworzonych, które są obsługiwane przez twój komponent. Na przykład, jeśli używasz komponentu FileSystemWatcher do monitorowania tworzenia nowych plików w katalogu, a następnie testujesz go za pomocą Notatnika, aby utworzyć plik, mogą zostać wygenerowane dwa zdarzenia Utworzone, mimo że utworzono tylko jeden plik. Wynika to z faktu, że Notatnik wykonuje wiele działań systemu plików podczas procesu zapisu. Notatnik zapisuje na dysk partiami, które tworzą zawartość pliku, a następnie atrybuty pliku. Inne aplikacje mogą działać w ten sam sposób. Ponieważ FileSystemWatcher monitoruje działania systemu operacyjnego, wszystkie zdarzenia uruchamiane przez te aplikacje zostaną wychwycone.

Teraz ten fragment tekstu dotyczy Createdzdarzenia, ale to samo dotyczy również innych zdarzeń plików. W niektórych aplikacjach możesz obejść ten problem, używającNotifyFilter właściwości, ale z mojego doświadczenia wynika, że ​​czasami trzeba również wykonać ręczne filtrowanie duplikatów (włamania).

Jakiś czas temu zarezerwowałem stronę z kilkoma wskazówkami FileSystemWatcher . Może będziesz chciał to sprawdzić.

Jørn Schou-Rode
źródło
6
Raymond Chen właśnie napisał o tym na blogu: Dlaczego zapisanie pliku w Notatniku uruchamia wiele zdarzeń FindFirstChangeNotification?
Cody Gray
151

„Naprawiłem” ten problem za pomocą następującej strategii u mojego delegata:

// 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;
   }
}
David Brabant
źródło
14
Próbowałem tego i zadziałało, jeśli modyfikowałem jeden plik naraz, ale jeśli modyfikowałem dwa pliki naraz (np. Kopiuj 1.txt i 2.txt do kopii 1.txt i kopii 2.txt), to tylko podniesie jedno wydarzenie nie dwa zgodnie z oczekiwaniami.
Christopher Painter
2
Minęło kilka miesięcy, ale myślę, że skończyło się na wywołaniu zdarzenia metodą, która umieszcza logikę biznesową w instrukcji blokady. W ten sposób, jeśli dostanę dodatkowe zdarzenia, ustawią się w kolejce, aż nadejdzie ich kolej i nie będą mieli nic do roboty, ponieważ poprzednia iteracja załatwiła wszystko.
Christopher Painter
15
To wydaje się rozwiązać problem, ale tak nie jest. Jeśli inny proces wprowadza zmiany, możesz je utracić, wydaje się, że działa ono dlatego, że IO innego procesu jest asynchroniczne i wyłączasz monitorowanie do momentu zakończenia przetwarzania, tworząc w ten sposób warunek wyścigu z innymi zdarzeniami, które mogą być zainteresowań. Właśnie dlatego @ChristopherPainter zauważył jego problem.
Jf Beaulac
14
-1: Co się stanie, jeśli inna zmiana, która Cię zainteresuje, nastąpi po wyłączeniu?
G. Stoynev
2
@cYounes: chyba że wykonujesz swoje rzeczy asynchronicznie.
David Brabant
107

Wszelkie zduplikowane OnChangedzdarzenia z FileSystemWatchermożna wykryć i odrzucić, sprawdzając File.GetLastWriteTimeznacznik czasu w danym pliku. Tak jak:

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
}
BaBu
źródło
13
Podoba mi się to rozwiązanie, ale użyłem Rx, aby zrobić „właściwą” rzecz (zmień "Rename"nazwę wydarzenia, które Cię interesuje):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski
4
Coś mi brakuje? Nie rozumiem, jak to będzie działać. Z tego, co widziałem, zdarzenia uruchamiają się jednocześnie, więc jeśli oba wejdą w powyższe zdarzenie w tym samym czasie, zaczną działać przed ustawieniem LastRead.
Peter Jamsmenson
Jak DateTimema tylko milisekundy rozdzielczość, metoda ta działa nawet jeśli zastąpi File.GetLastWriteTimesię DateTime.Now. W zależności od sytuacji możesz także użyć a.FullNamezmiennej globalnej w celu wykrycia zduplikowanych zdarzeń.
Roland,
@PeterJamsmenson Zdarzenia nie są uruchamiane jednocześnie. Na przykład Notatnik może generować kilka zdarzeń podczas zapisywania modyfikacji na dysku, ale te zdarzenia są kolejno uruchamiane, jeden po drugim, podczas kilku kroków, które Notepad musi wykonać w celu zapisania. Metoda Babu działa świetnie.
Roland,
10
Nie działa, ponieważ uruchomione zdarzenia są oddzielone tykami: Czas ostatniego zapisu: 636076274162565607 Czas ostatniego zapisu: 636076274162655722
Asheh
23

Oto moje rozwiązanie, które pomogło mi zatrzymać wydarzenie dwukrotnie:

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

Tutaj ustawiłem NotifyFilterwłaściwość tylko z nazwą pliku i rozmiarem.
watcherjest moim obiektem FileSystemWatcher. Mam nadzieję, że to pomoże.

Deepashri
źródło
9
Ponadto w Notatniku utworzyłem plik z czterema znakami: abcd. Następnie otworzyłem nową instancję Notatnika i wprowadziłem te same cztery znaki. Wybrałem plik | Zapisz jako i wybierz ten sam plik. Plik jest identyczny, a rozmiar i nazwa pliku nie zmieniają się, ponieważ plik ma te same cztery litery, więc nie uruchamia się.
Rhyous,
30
Możliwe, że zostanie wprowadzona prawdziwa zmiana, która nie zmieni rozmiaru pliku, dlatego ta technika zawiódłaby w takiej sytuacji.
Lee Grissom,
3
Sądzę, że jest to dość powszechny przypadek, w którym wiesz, że każda znacząca zmiana zmodyfikuje rozmiar pliku (na przykład moja sprawa dołączała się do pliku dziennika). Chociaż każdy, kto korzysta z tego rozwiązania, powinien mieć świadomość (i udokumentować) to założenie, tego właśnie potrzebowałem.
GrandOpener
1
@GrandOpener: Nie zawsze jest to prawda. W moim przypadku oglądam pliki, których zawartość składa się tylko z jednego znaku, który ma wartość 0 lub 1.
8

Mój scenariusz jest taki, że mam maszynę wirtualną z serwerem Linux. Tworzę pliki na hoście Windows. Kiedy zmieniam coś w folderze na hoście, chcę, aby wszystkie zmiany zostały przesłane, zsynchronizowane z serwerem wirtualnym przez Ftp. W ten sposób eliminuję zduplikowane zdarzenie zmiany, gdy piszę do pliku (który oznacza również folder zawierający plik do zmodyfikowania):

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

Głównie tworzę hashtable do przechowywania informacji o czasie zapisu pliku. Następnie, jeśli tablica skrótów ma zmodyfikowaną ścieżkę pliku, a jej wartość czasu jest taka sama, jak w przypadku zmiany pliku, który jest aktualnie powiadamiany, to wiem, że to duplikat zdarzenia i zignoruj ​​go.

Ikon
źródło
Zakładam, że okresowo opróżniasz hashtable.
ThunderGr
Byłoby to dokładne z dokładnością do sekundy, ale jeśli okres między dwiema zmianami jest wystarczająco długi, aby minąć sekundę, to się nie powiedzie. Ponadto, jeśli chcesz uzyskać większą dokładność, możesz użyć, ToString("o")ale bądź przygotowany na więcej awarii.
Pragmateek
5
Nie porównuj ciągów, użyj DateTime.Equals ()
Phillip Kamikaze
Nie, nie rób tego. Nie są równi. W przypadku mojego obecnego projektu dzieli je około milisekunda. Używam (newtime-oldtime) .TotalMilliseconds <(arbitralny próg, zwykle 5ms).
Flynn1179,
8

Spróbuj użyć tego kodu:

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;
    }
}
Jamie Krcmar
źródło
1
To najlepsze rozwiązanie, używając semafora zamiast timera.
Aaron Blenkush,
1
Jaki semafor? Widzę tu tylko zmienną boolowską. Ponadto główny problem nie został rozwiązany: FileSystemEventHandler nadal uruchamia wiele zdarzeń. A jaką skuteczność ma ten kod? if (let==false) { ... } else { let = false; }? Niesamowite, jak to się zyskało, to tylko kwestia odznak StackOverflow.
sɐunıɔ ןɐ qɐp
8

Oto moje podejście:

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

Jest to rozwiązanie, którego użyłem do rozwiązania tego problemu w projekcie, w którym wysyłałem plik jako załącznik w wiadomości e-mail. Z łatwością pozwoli uniknąć podwójnie uruchomionego zdarzenia, nawet przy mniejszym interwale czasowym, ale w moim przypadku 1000 było w porządku, ponieważ byłem szczęśliwszy z pominięcia kilku zmian niż z zalania skrzynki pocztowej> 1 wiadomością na sekundę. Przynajmniej działa dobrze, jeśli kilka plików zostanie zmienionych w tym samym czasie.

Innym rozwiązaniem, o którym myślałem, byłoby zastąpienie listy plikami mapowania słownika do ich odpowiednich MD5, więc nie musiałbyś wybierać dowolnego interwału, ponieważ nie musiałbyś usuwać wpisu, ale aktualizować jego wartość, i anuluj swoje rzeczy, jeśli się to nie zmieniło. Ma to tę wadę, że Słownik rośnie w pamięci, ponieważ pliki są monitorowane i zjadają coraz więcej pamięci, ale czytałem gdzieś, że ilość monitorowanych plików zależy od bufora wewnętrznego FSW, więc może nie jest to tak ważne. Nie wiem, jak czas obliczeń MD5 wpłynie na wydajność twojego kodu, ostrożnie = \

Rémy Esmery
źródło
Twoje rozwiązanie działa dla mnie świetnie. Tylko zapomniałeś dodać plik do listy _changedFiles. Pierwsza część kodu powinna wyglądać następująco:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey
Zanotowałem 4 odpowiedzi powyżej i poparłem tę jedną. Twoja odpowiedź jest pierwszą, która robi to, co powinna, biorąc udział w wydarzeniu OSTATNIE, a nie pierwszą. Jak wyjaśniono w @Jorn, problem polega na tym, że pliki są zapisywane partiami. Inne rozwiązania nie działały dla mnie.
CodingYourLife
Twoje rozwiązanie nie jest bezpieczne dla wątków. _changedFilesJest dostępne z wielu wątków. Jednym ze sposobów, aby to naprawić, jest użycie ConcurrentDictionaryzamiast List. Innym sposobem jest przypisanie prądu Formdo Timer.SynchronizingObjectwłaściwości, a także do FileSystemWatcher.SynchronizingObjectwłaściwości.
Theodor Zoulias
5

Utworzyłem repozytorium Git z klasą, która rozciąga się FileSystemWatcherna wyzwalanie zdarzeń tylko po zakończeniu kopiowania. Odrzuca wszystkie zmienione zdarzenia oprócz ostatniego i podnosi je dopiero, gdy plik będzie dostępny do odczytu.

Pobierz FileSystemSafeWatcher i dodaj go do swojego projektu.

Następnie użyj go jak normalnie FileSystemWatcheri monitoruj, kiedy zdarzenia zostaną wyzwolone.

var fsw = new FileSystemSafeWatcher(file);
fsw.EnableRaisingEvents = true;
// Add event handlers here
fsw.Created += fsw_Created;
Menelaos Vergis
źródło
Wydaje się, że kończy się to niepowodzeniem, gdy zdarzenie zostanie wywołane w katalogu. Udało mi się to, owijając sprawdzanie katalogu przed otwarciem pliku
Sam
Mimo literówki w tym przykładzie wydaje mi się to realnym rozwiązaniem. Jednak w moim przypadku może być kilkanaście aktualizacji w ciągu jednej sekundy, więc musiałem drastycznie obniżyć Interwał konsolidacji, aby nie przegapić żadnych zmian. Chociaż 10 ms wydaje się być w porządku, nadal tracę około 50% aktualizacji, jeśli ustawię _consolidationInterval na 50 ms. Nadal muszę przeprowadzić kilka testów, aby znaleźć wartość, która najlepiej pasuje.
_consolidationInterval wydaje się działać dobrze dla mnie. Chciałbym, żeby ktoś to rozwidlił i uczynił z niego pakiet NuGet.
zumalifeguard
1
Dzięki :) To rozwiązało mój problem. Mam nadzieję, że utworzone i skopiowane zdarzenia będą działać poprawnie z jednym obserwatorem, aby dobrze rozwiązać ten problem. stackoverflow.com/questions/55015132/…
techno
1
To jest doskonałe. Zaimplementowałem go w moim projekcie i pobił każdą próbę jego złamania. Dziękuję Ci.
Christh
4

Wiem, że to stary problem, ale miałem ten sam problem i żadne z powyższych rozwiązań nie rozwiązało problemu, z którym miałem do czynienia. Utworzyłem słownik, który mapuje nazwę pliku za pomocą LastWriteTime. Jeśli więc pliku nie ma w słowniku, przejdziemy do następnego kroku, aby sprawdzić, kiedy był ostatnio modyfikowany czas i czy różni się od tego, co znajduje się w słowniku, uruchom kod.

    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
                }
            }
Zardaloop
źródło
To solidne rozwiązanie, ale brakuje mu linii kodu. w your code heresekcji powinieneś dodać lub zaktualizować dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake,
Nie działało dla mnie. Mój moduł obsługi zmian jest wywoływany dwukrotnie, a plik ma inny znacznik czasu za drugim razem. Może tak być, ponieważ jest to duży plik, a zapis był w toku za pierwszym razem. Znalazłem zegar, aby zwinąć zduplikowane zdarzenia działał lepiej.
Michał
3

Jednym z możliwych „hacków” byłoby zdławienie zdarzeń za pomocą Reactive Extensions na przykład:

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

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

watcher.EnableRaisingEvents = true;

W tym przypadku dławię do 50 ms, w moim systemie to wystarczyło, ale wyższe wartości powinny być bezpieczniejsze. (I jak powiedziałem, to wciąż „hack”).

TimothyP
źródło
Użyłem, z .Distinct(e => e.FullPath)którym uważam, że jest bardziej intuicyjny w obsłudze. I przywrócono zachowanie, którego można oczekiwać od interfejsu API.
Kjellski
3

Mam tutaj bardzo szybkie i proste obejście, działa dla mnie i bez względu na to, czy zdarzenie zostanie uruchomione raz, dwa lub więcej razy, sprawdź to:

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;
        }
    }
}
Xiaoyuvax
źródło
Na początku myślałem, że to zadziała, ale tak nie jest. Mam sytuację, w której zawartość pliku jest czasami po prostu nadpisywana, a innym razem plik jest usuwany i ponownie tworzony. Chociaż wydaje się, że Twoje rozwiązanie działa w przypadku zastąpienia pliku, nie zawsze działa w przypadku odtworzenia pliku. W tym drugim przypadku zdarzenia czasem giną.
Spróbuj uporządkować różne rodzaje zdarzeń i poradzić sobie z nimi osobno, po prostu oferuję możliwe obejście. powodzenia.
Xiaoyuvax,
chociaż go nie testuję, nie jestem pewien, czy to nie działa w przypadku tworzenia i usuwania. powinien również mieć zastosowanie teoretyczne. Ponieważ instrukcja fireCount ++ i if () są zarówno atomowe, jak i nie będą czekać. nawet z dwoma wyzwalanymi wydarzeniami, które konkurują ze sobą. myślę, że musi być coś innego, co może spowodować problemy. (przez zgubiony? co masz na myśli?)
Xiaoyuvax
3

Oto nowe rozwiązanie, które możesz wypróbować. Działa dla mnie dobrze. W module obsługi zdarzenia dla zmienionego zdarzenia programowo usuń moduł obsługi z wyjścia projektanta, w razie potrzeby, a następnie programowo dodaj moduł obsługi z powrotem. przykład:

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 );
    }
Fancy_Mammoth
źródło
1
Nie trzeba wywoływać konstruktora typu delegata. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;powinien zrobić właściwą rzecz.
bartonjs
@bartonjs Dzięki za to. Nie jestem pewien, dlaczego wywołałem całego konstruktora. Szczerze mówiąc, najprawdopodobniej jest to błąd początkującego. Niezależnie od tego wydaje się, że mój hack poprawki działał całkiem dobrze.
Fancy_Mammoth
2

Głównym powodem, dla którego ostatni raz dostęp do pierwszego zdarzenia był bieżący (czas zapisu lub zmiany pliku). drugim zdarzeniem był pierwotny czas ostatniego dostępu do pliku. Rozwiązuję pod kodem.

        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;
Kim Ki Won
źródło
taka sama jak ta odpowiedź
Jean-Paul
2

Spędziłem sporo czasu przy użyciu FileSystemWatcher, a niektóre z podejść tutaj nie będą działać. Bardzo podobało mi się podejście polegające na wyłączaniu zdarzeń, ale niestety nie działa, jeśli upuszczono> 1 plik, drugi plik zostanie pominięty najbardziej, jeśli nie cały czas. Dlatego stosuję następujące podejście:

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.
}
Gino
źródło
2

Ten kod działał dla mnie.

        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);
    }
Neo
źródło
2

głównie dla mnie przyszłości :)

Napisałem opakowanie za pomocą 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();
    }
}

Stosowanie:

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)
Krzysztof Skowronek
źródło
1

Zmieniłem sposób monitorowania plików w katalogach. Zamiast używać FileSystemWatcher sonduję lokalizacje w innym wątku, a następnie patrzę na LastWriteTime pliku.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

Korzystając z tych informacji i prowadząc indeks ścieżki pliku oraz czas ostatniego zapisu, mogę określić pliki, które uległy zmianie lub zostały utworzone w określonej lokalizacji. To usuwa mnie z osobliwości FileSystemWatcher. Główną wadą jest to, że potrzebujesz struktury danych do przechowywania LastWriteTime i odwołania do pliku, ale jest to niezawodne i łatwe do wdrożenia.

Wil P.
źródło
9
a także trzeba nagrywać cykle w tle zamiast być powiadamianym przez zdarzenie systemowe.
Matthew Whited,
1

Możesz spróbować otworzyć go do zapisu, a jeśli się powiedzie, możesz założyć, że inna aplikacja została wykonana z plikiem.

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

Samo otwarcie go do zapisu wydaje się nie wywoływać zmienionego zdarzenia. Więc powinno być bezpiecznie.

MatteKarla
źródło
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;
    }
}
prasad
źródło
1
Chociaż może to być najlepsze rozwiązanie zadanego pytania, zawsze fajnie jest dodać kilka komentarzy na temat tego, dlaczego wybrałeś to podejście i dlaczego uważasz, że to działa. :)
waka
1

Przepraszam za wykopaliska, ale od jakiegoś czasu walczę z tym problemem i wreszcie znalazłem sposób na poradzenie sobie z tymi kilkoma wystrzelonymi zdarzeniami. Chciałbym podziękować wszystkim w tym wątku, ponieważ użyłem go w wielu referencjach podczas walki z tym problemem.

Oto mój pełny kod. Używa słownika do śledzenia daty i godziny ostatniego zapisu pliku. Porównuje tę wartość, a jeśli jest taka sama, tłumi zdarzenia. Następnie ustawia wartość po rozpoczęciu nowego wątku.

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" );
        }
    }
BinaryAssault
źródło
Wykorzystałem to w jednym z moich projektów. Działa świetnie!
Tyler Montney,
Nie działa, ponieważ uruchomione zdarzenia są oddzielone tykami: Ostatni czas zapisu: 636076274162565607 Ostatni czas zapisu: 636076274162655722
Profesor programowania
1

Jeśli nie zostaniesz zapytany, szkoda, że ​​nie ma gotowych próbek rozwiązania dla F #. Aby to naprawić, oto mój przepis, tylko dlatego, że mogę, a F # to wspaniały język .NET.

Zduplikowane zdarzenia są odfiltrowywane za pomocą FSharp.Control.Reactivepakietu, który jest tylko opakowaniem F # dla reaktywnych rozszerzeń. Wszystko to może być ukierunkowane na pełne ramy lub 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
python_kaa
źródło
1

W moim przypadku muszę uzyskać ostatnią linię pliku tekstowego, który jest wstawiany przez inną aplikację, jak tylko wstawianie zostanie wykonane. Oto moje rozwiązanie. Kiedy pierwsze zdarzenie jest wywoływane, wyłączam obserwator od podnoszenia innych, a następnie wywołuję timer TimeElapsedEvent, ponieważ kiedy moja funkcja uchwytu OnChanged jest wywoływana, potrzebuję rozmiaru pliku tekstowego, ale rozmiar w tym czasie nie jest rzeczywistym rozmiarem, jest to rozmiar pliku bezpośrednio przed wstawieniem. Więc czekam chwilę, aby przejść do właściwego rozmiaru pliku.

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;
}
André Washington
źródło
1

Spróbuj tego, działa dobrze

  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;
        }
    }
Rozpoznać
źródło
1

Chciałem zareagować tylko na ostatnie zdarzenie, na wszelki wypadek, również przy zmianie pliku linux wydawało się, że plik był pusty przy pierwszym wywołaniu, a następnie wypełniany ponownie przy następnym, i nie miałem nic przeciwko stracie czasu na wypadek, gdyby system operacyjny postanowiłem dokonać zmiany pliku / atrybutu.

Korzystam z asynchronizacji platformy .NET, aby pomóc mi w tworzeniu wątków.

    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();
    }
Guillermo Ruffino
źródło
1

Myślę, że najlepszym rozwiązaniem do rozwiązania tego problemu jest użycie rozszerzeń reaktywnych Po przekształceniu zdarzenia w obserwowalne, możesz po prostu dodać Ograniczanie (..) (pierwotnie zwane Debounce (..))

Przykładowy kod tutaj

        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
            });
Cek Szy
źródło
0

Byłem w stanie to zrobić, dodając funkcję, która sprawdza duplikaty w tablicy buforów.

Następnie wykonaj akcję po tym, jak tablica nie zostanie zmodyfikowana na czas X za pomocą timera: - Resetuj timer za każdym razem, gdy coś jest zapisywane w buforze - Wykonaj akcję po zaznaczeniu

Przechwytuje to także inny typ duplikacji. Jeśli zmodyfikujesz plik w folderze, folder zgłasza również zdarzenie Zmień.

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
ślepy
źródło
0

To rozwiązanie działało dla mnie w aplikacji produkcyjnej:

Środowisko:

VB.Net Framework 4.5.2

Ustaw ręcznie właściwości obiektu: NotifyFilter = Rozmiar

Następnie użyj tego kodu:

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
wpcoder
źródło
Używa tej samej koncepcji jak @Jamie Krcmar, ale dla VB.NET
wpcoder
0

Spróbuj tego!

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

}
LT
źródło
0

Musiałem połączyć kilka pomysłów z powyższych postów i dodać kontrolę blokowania plików, aby działała dla mnie:

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
    }
}
HarryP
źródło
0

Podszedłem do problemu podwójnego tworzenia, który ignoruje pierwsze zdarzenie:

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
Simon Barnett
źródło