L'événement FileSystemWatcher Changed est déclenché deux fois


336

J'ai une application dans laquelle je recherche un fichier texte et s'il y a des modifications apportées au fichier, j'utilise le OnChangedgestionnaire d'événements pour gérer l'événement. J'utilise le NotifyFilters.LastWriteTimemais l'événement est toujours renvoyé deux fois. Voici le 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)
{
   .......
}

Dans mon cas, le OnChangedest appelé deux fois, lorsque je change le fichier texte version.txtet l'enregistre.


2
@BrettRigby: Pas étonnant. Aucune de ces réponses potentielles ne fournit la solution au problème. Ce sont tous des solutions de contournement pour des problèmes spécifiques. En fait, aucun d'eux n'a résolu mon problème spécifique (je dois admettre que je ne les ai pas tous testés).

Il s'agit d'une solution de contournement, mais elle doit être jugée par la qualité de la solution de contournement. Garder une trace des changements fonctionne parfaitement et c'est simple. OP demande un moyen de supprimer les événements en double, et c'est ce que donnent les réponses ci-dessous. msdn.microsoft.com/en-us/library/… Explique que les événements multiples peuvent être causés par un antivirus ou d'autres "trucs compliqués du système de fichiers" (ce qui sonne juste comme une excuse).
Tyler Montney

2
J'ai récemment ouvert ce problème github.com/Microsoft/dotnet/issues/347
Stephan Ahlf

2
J'ai créé une classe qui vous aide à obtenir un seul événement. Vous pouvez obtenir le code sur github.com/melenaos/FileSystemSafeWatcher
Menelaos Vergis

Réponses:


277

Je crains que ce soit un bug / fonctionnalité bien connu de la FileSystemWatcherclasse. Ceci provient de la documentation de la classe:

Vous pouvez remarquer dans certaines situations qu'un seul événement de création génère plusieurs événements créés qui sont gérés par votre composant. Par exemple, si vous utilisez un composant FileSystemWatcher pour surveiller la création de nouveaux fichiers dans un répertoire, puis testez-le à l'aide du Bloc-notes pour créer un fichier, vous pouvez voir deux événements créés générés même si un seul fichier a été créé. Cela est dû au fait que le Bloc-notes effectue plusieurs actions du système de fichiers pendant le processus d'écriture. Le bloc-notes écrit sur le disque par lots qui créent le contenu du fichier, puis les attributs du fichier. D'autres applications peuvent fonctionner de la même manière. Étant donné que FileSystemWatcher surveille les activités du système d'exploitation, tous les événements déclenchés par ces applications seront récupérés.

Maintenant, ce morceau de texte concerne l' Createdévénement, mais la même chose s'applique également aux autres événements de fichier. Dans certaines applications, vous pouvez peut-être contourner ce problème en utilisant la NotifyFilterpropriété, mais mon expérience montre que parfois, vous devez également effectuer un filtrage manuel des doublons (hacks).

Il y a quelque temps, j'ai réservé une page avec quelques conseils FileSystemWatcher . Vous voudrez peut-être le vérifier.



151

J'ai "résolu" ce problème en utilisant la stratégie suivante dans mon délégué:

// 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
J'ai essayé cela et cela a fonctionné si je modifiais un fichier à la fois mais si je modifiais deux fichiers à la fois (comme copier 1.txt et 2.txt pour copier 1.txt et copier 2.txt) cela ne ferait que lever un événement pas deux comme prévu.
Christopher Painter,

2
Cela fait quelques mois, mais je pense que ce que j'ai fini par faire, c'est que l'événement appelle une méthode qui place la logique métier dans une instruction de verrouillage. De cette façon, si j'obtiens des événements supplémentaires, ils font la queue jusqu'à leur tour et il n'y a rien à faire depuis l'itération précédente qui s'est occupée de tout.
Christopher Painter

15
Cela semble résoudre le problème, mais ce n'est pas le cas. Si un autre processus apporte des modifications, vous risquez de les perdre, la raison pour laquelle cela semble fonctionner est que l'IO de l'autre processus est asynchrone et que vous désactivez la surveillance jusqu'à ce que vous ayez terminé votre traitement, créant ainsi une condition de concurrence avec d'autres événements qui pourraient être d'intérêt. C'est pourquoi @ChristopherPainter a observé son problème.
Jf Beaulac

14
-1: Que se passe-t-il si un autre changement qui vous intéresse se produit lorsque vous êtes désactivé?
G.Stoynev

2
@cYounes: à moins que vous ne fassiez vos trucs de manière asynchrone.
David Brabant

107

Tous les OnChangedévénements dupliqués FileSystemWatcherpeuvent être détectés et supprimés en vérifiant l' File.GetLastWriteTimehorodatage du fichier en question. Ainsi:

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
J'aime cette solution, mais j'ai utilisé Rx pour faire la "bonne" chose (changer "Rename"le nom de l'événement qui vous intéresse):Observable.FromEventPattern<FileSystemEventArgs>(fileSystemWatcher, "Renamed") .Select(e => e.EventArgs) .Distinct(e => e.FullPath) .Subscribe(onNext);
Kjellski

4
Suis-je en train de manquer quelque chose? Je ne comprends pas comment cela fonctionnera. D'après ce que j'ai vu les événements se déclencher simultanément, donc s'ils entrent tous les deux dans l'événement ci-dessus en même temps, ils commenceront tous les deux à fonctionner avant que lastRead ne soit défini.
Peter Jamsmenson

Comme DateTimeseule la résolution est en millisecondes, cette méthode fonctionne même si vous la remplacez File.GetLastWriteTimepar DateTime.Now. Selon votre situation, vous pouvez également utiliser le a.FullNamedans une variable globale pour détecter les événements en double.
Roland

@PeterJamsmenson Les événements ne se déclenchent pas exactement simultanément. Par exemple, le Bloc-notes peut générer plusieurs événements lors de l'enregistrement des modifications sur le disque, mais ces événements sont déclenchés séquentiellement, l'un après l'autre, au cours des différentes étapes que le Bloc-notes doit effectuer pour une sauvegarde. La méthode de Babu fonctionne très bien.
Roland

10
Ne fonctionne pas car les événements déclenchés sont séparés: Dernière heure d'écriture: 636076274162565607 Dernière heure d'écriture: 636076274162655722
Asheh

23

Voici ma solution qui m'a aidé à éviter que l'événement ne soit déclenché deux fois:

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

Ici, j'ai défini la NotifyFilterpropriété avec uniquement le nom de fichier et la taille.
watcherest mon objet de FileSystemWatcher. J'espère que cela vous aidera.


9
De plus, dans le Bloc-notes, j'ai créé un fichier avec quatre caractères: abcd dedans. J'ai ensuite ouvert une nouvelle instance du Bloc-notes et entré les quatre mêmes caractères. J'ai choisi Fichier | Enregistrez sous et choisissez le même fichier. Le fichier est identique et la taille et le nom de fichier ne changent pas, car le fichier a les mêmes quatre lettres, donc cela ne se déclenche pas.
Rhyous

30
Il est possible qu'un véritable changement puisse être effectué qui ne modifie pas la taille du fichier, donc cette technique échouerait dans cette situation.
Lee Grissom

3
Je suppose que c'est un cas assez courant où vous savez que tout changement significatif modifiera la taille du fichier (par exemple, mon cas s'ajoutait à un fichier journal). Quiconque utilise cette solution doit être conscient (et documenter) cette hypothèse, c'est exactement ce dont j'avais besoin.
GrandOpener

1
@GrandOpener: Ce n'est pas toujours vrai. Dans mon cas, je regarde des fichiers dont le contenu se compose d'un seul caractère, soit 0 ou 1.

8

Mon scénario est que j'ai une machine virtuelle avec un serveur Linux dedans. Je développe des fichiers sur l'hôte Windows. Lorsque je change quelque chose dans un dossier sur l'hôte, je veux que toutes les modifications soient téléchargées, synchronisées sur le serveur virtuel via Ftp. Voici comment j'élimine l'événement de modification en double lorsque j'écris dans un fichier (qui marque également le dossier contenant le fichier à modifier):

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

Je crée principalement une table de hachage pour stocker les informations de temps d'écriture des fichiers. Ensuite, si la table de hachage a le chemin de fichier qui est modifié et sa valeur de temps est la même que la modification du fichier actuellement notifié, alors je sais que c'est le doublon de l'événement et je l'ignore.


Je suppose que vous videz régulièrement la table de hachage.
ThunderGr

Ce serait précis pour la seconde mais si la période entre les deux changements est suffisamment longue pour passer une seconde, elle échouera. De plus, si vous voulez plus de précision, vous pouvez utiliser, ToString("o")mais soyez prêt pour plus d'échecs.
Pragmateek

5
Ne comparez pas les chaînes, utilisez DateTime.Equals ()
Phillip Kamikaze

Non, non. Ils ne sont pas égaux. Dans le cas de mon projet actuel, ils sont à environ une milliseconde d'intervalle. J'utilise (newtime-oldtime) .TotalMilliseconds <(seuil arbitraire, généralement 5 ms).
Flynn1179

8

Essayez avec ce 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
C'est la meilleure solution, en utilisant un sémaphore au lieu d'une minuterie.
Aaron Blenkush

1
Quel sémaphore? Je vois juste une variable booléenne ici. De plus, le problème principal n'est pas résolu: FileSystemEventHandler déclenche toujours plusieurs événements. Et quelle efficacité a ce code? if (let==false) { ... } else { let = false; }? Incroyable comment cela a obtenu des votes positifs, cela ne doit être qu'une question de badges StackOverflow.
sɐunıɔ ןɐ qɐp

8

Voici mon approche:

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

C'est la solution que j'ai utilisée pour résoudre ce problème sur un projet où j'envoyais le fichier en pièce jointe dans un courrier. Il évitera facilement l'événement déclenché deux fois, même avec un intervalle de temporisation plus petit, mais dans mon cas, 1000 était correct car j'étais plus heureux de manquer quelques modifications que d'inonder la boîte aux lettres avec> 1 message par seconde. Au moins, cela fonctionne très bien au cas où plusieurs fichiers sont modifiés en même temps.

Une autre solution à laquelle j'ai pensé serait de remplacer la liste par un dictionnaire mappant les fichiers vers leur MD5 respectif, de sorte que vous n'auriez pas à choisir un intervalle arbitraire car vous n'auriez pas à supprimer l'entrée mais à mettre à jour sa valeur, et annulez votre contenu s'il n'a pas changé. Il a l'inconvénient d'avoir un dictionnaire croissant en mémoire à mesure que les fichiers sont surveillés et consommant de plus en plus de mémoire, mais j'ai lu quelque part que la quantité de fichiers surveillés dépend du tampon interne du FSW, donc peut-être pas si critique. Ne sais pas non plus comment le temps de calcul MD5 affecterait les performances de votre code, attention = \


Votre solution fonctionne très bien pour moi. Seulement, vous avez oublié d'ajouter le fichier à la liste _changedFiles. La première partie du code devrait ressembler à ceci:lock (_changedFiles) { if (_changedFiles.Contains(e.FullPath)) { return; } _changedFiles.Add(e.FullPath); // add this! } // do your stuff
davidthegrey

J'ai rétrogradé 4 réponses ci-dessus et voté pour celle-ci. Votre réponse est la première à faire ce qu'elle devrait faire en prenant le DERNIER événement et non le premier. Comme expliqué par @Jorn, le problème est que les fichiers sont écrits par lots. D'autres solutions n'ont pas fonctionné pour moi.
CodingYourLife

Votre solution n'est pas thread-safe. L' _changedFilesaccès à partir de plusieurs threads. Une façon de résoudre ce problème consiste à utiliser un ConcurrentDictionaryau lieu de List. Une autre façon consiste à affecter le courant Formà la Timer.SynchronizingObjectpropriété, ainsi qu'à la FileSystemWatcher.SynchronizingObjectpropriété.
Theodor Zoulias

5

J'ai créé un dépôt Git avec une classe qui s'étend FileSystemWatcherpour déclencher les événements uniquement lorsque la copie est effectuée. Il supprime tous les événements modifiés, sauf le dernier, et il ne le déclenche que lorsque le fichier est disponible en lecture.

Téléchargez FileSystemSafeWatcher et ajoutez-le à votre projet.

Ensuite, utilisez-le comme un normal FileSystemWatcheret surveillez le déclenchement des événements.

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

Cela semble échouer lorsqu'un événement est déclenché sur un répertoire. Je l'ai fait fonctionner en encapsulant une vérification de répertoire avant d'ouvrir le fichier
Sam

Malgré la faute de frappe dans l'exemple, cela semble être une solution viable pour moi. Cependant, dans mon cas, il peut y avoir une douzaine de mises à jour en une seconde, j'ai donc dû réduire considérablement _consolidationInterval pour ne manquer aucune modification. Bien que 10 ms semblent corrects, je perds toujours environ 50% des mises à jour si je définis _consolidationInterval sur 50 ms. Je dois encore exécuter des tests pour trouver la valeur qui convient le mieux.

_consolidationInterval semble bien fonctionner pour moi. Je voudrais que quelqu'un crée ceci et en fasse un package NuGet.
zumalifeguard

1
Merci :) Cela a résolu mon problème. J'espère que les événements créés et copiés fonctionneront correctement avec un seul observateur pour bien résoudre ce problème. stackoverflow.com/questions/55015132/…
techno

1
C'est excellent. Je l'ai implémenté dans mon projet et il a battu toutes les tentatives que j'ai faites pour essayer de le casser. Je vous remercie.
Christh

4

Je sais que c'est un vieux problème, mais j'ai eu le même problème et aucune des solutions ci-dessus n'a vraiment fait l'affaire pour le problème auquel j'étais confronté. J'ai créé un dictionnaire qui mappe le nom de fichier avec LastWriteTime. Donc, si le fichier n'est pas dans le dictionnaire, poursuivez le processus, vérifiez autrement pour voir quand a été la dernière modification et s'il est différent de ce qu'il est dans le dictionnaire, exécutez le code.

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

C'est une solution solide, mais il manque une ligne de code. dans la your code heresection, vous devez ajouter ou mettre à jour le dateTimeDictionary. dateTimeDictionary[e.FullPath] = System.IO.File.GetLastWriteTime(e.FullPath);
DiamondDrake

N'a pas travaillé pour moi. Mon gestionnaire de modifications est appelé deux fois et le fichier a un horodatage différent la deuxième fois. Peut-être parce qu'il s'agit d'un fichier volumineux et que l'écriture était en cours la première fois. J'ai trouvé une minuterie pour réduire les événements en double qui fonctionnait mieux.
michael

3

Un «hack» possible serait de limiter les événements en utilisant des extensions réactives, par exemple:

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

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

watcher.EnableRaisingEvents = true;

Dans ce cas, je passe à 50 ms, sur mon système, c'était suffisant, mais des valeurs plus élevées devraient être plus sûres. (Et comme je l'ai dit, c'est toujours un «hack»).


J'ai utilisé .Distinct(e => e.FullPath)ce que je trouve beaucoup plus intuitif. Et vous avez restauré le comportement attendu de l'API.
Kjellski

3

J'ai une solution de contournement très rapide et simple ici, cela fonctionne pour moi, et peu importe que l'événement soit déclenché une ou deux fois ou plusieurs fois de temps en temps, vérifiez-le:

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

Au début, je pensais que cela fonctionnerait pour moi, mais ce n'est pas le cas. J'ai une situation où le contenu des fichiers est parfois simplement écrasé et d'autres fois le fichier est supprimé et recréé. Bien que votre solution semble fonctionner si le fichier est écrasé, elle ne fonctionne pas toujours si le fichier est recréé. Dans ce dernier cas, les événements se perdent parfois.

Essayez de trier les différents types d'événements et de les traiter séparément, je propose simplement une solution de contournement possible. bonne chance.
Xiaoyuvax

mais pas le tester, je ne suis pas sûr que cela ne fonctionne pas pour la création et la suppression. il devrait également être théoriquement applicable. Étant donné que les instructions fireCount ++ et if () sont toutes deux atomiques et ne seront pas mises en attente. même avec deux événements déclenchés qui se font concurrence. je suppose qu'il doit y avoir autre chose qui cause votre problème. (par perdu? que voulez-vous dire?)
Xiaoyuvax

3

Voici une nouvelle solution que vous pouvez essayer. Fonctionne bien pour moi. Dans le gestionnaire d'événements de l'événement modifié, supprimez par programme le gestionnaire du concepteur, affichez un message si vous le souhaitez, puis rajoutez par programme le gestionnaire. exemple:

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
Vous n'avez pas besoin d'appeler le constructeur du type délégué. this.fileSystemWatcher1.Changed -= this.fileSystemWatcher1_Changed;devrait faire la bonne chose.
bartonjs

@bartonjs Merci pour cela. Je ne sais pas pourquoi j'ai invoqué le constructeur entier. Honnêtement, c'est probablement une erreur de débutant. Quoi qu'il en soit, il semble que mon hack d'un correctif ait assez bien fonctionné.
Fancy_Mammoth

2

La raison principale était que le dernier accès du premier événement était l'heure actuelle (écriture de fichier ou heure modifiée). alors le deuxième événement était le dernier temps d'accès d'origine du fichier. Je résous sous le 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;


2

J'ai passé beaucoup de temps à utiliser FileSystemWatcher, et certaines des approches ici ne fonctionneront pas. J'ai vraiment aimé l'approche des événements désactivants, mais malheureusement, cela ne fonctionne pas s'il y a> 1 fichier en cours de suppression, le deuxième fichier sera manqué la plupart sinon tous les temps. J'utilise donc l'approche suivante:

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

Ce code a fonctionné pour moi.

        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

surtout pour l'avenir moi :)

J'ai écrit un wrapper en utilisant 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();
    }
}

Usage:

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

J'ai changé la façon dont je surveille les fichiers dans les répertoires. Au lieu d'utiliser FileSystemWatcher, j'interroge les emplacements sur un autre thread, puis regarde le LastWriteTime du fichier.

DateTime lastWriteTime = File.GetLastWriteTime(someFilePath);

En utilisant ces informations et en gardant un index d'un chemin de fichier et de sa dernière heure d'écriture, je peux déterminer les fichiers qui ont changé ou qui ont été créés dans un emplacement particulier. Cela me retire des bizarreries du FileSystemWatcher. Le principal inconvénient est que vous avez besoin d'une structure de données pour stocker le LastWriteTime et la référence au fichier, mais elle est fiable et facile à implémenter.


9
ainsi que vous devez graver des cycles d'arrière-plan au lieu d'être averti par un événement système.
Matthew Whited,

1

Vous pouvez essayer de l'ouvrir pour l'écriture, et en cas de succès, vous pouvez supposer que l'autre application a terminé avec le fichier.

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

L'ouvrir pour l'écriture ne semble pas déclencher l'événement modifié. Il devrait donc être sûr.


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
Bien que cela puisse être la meilleure solution à la question posée, il est toujours agréable d'ajouter quelques commentaires sur les raisons pour lesquelles vous avez choisi cette approche et pourquoi vous pensez qu'elle fonctionne. :)
waka

1

Désolé pour la fouille, mais je lutte contre ce problème depuis un certain temps maintenant et j'ai finalement trouvé un moyen de gérer ces multiples événements déclenchés. Je tiens à remercier tout le monde dans ce fil car je l'ai utilisé dans de nombreuses références lors de la lutte contre ce problème.

Voici mon code complet. Il utilise un dictionnaire pour suivre la date et l'heure de la dernière écriture du fichier. Il compare cette valeur, et si elle est la même, elle supprime les événements. Il définit ensuite la valeur après le démarrage du nouveau 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" );
        }
    }

Utilisé ceci dans l'un de mes projets. Fonctionne très bien!
Tyler Montney

Ne fonctionne pas car les événements déclenchés sont séparés: Dernière heure d'écriture: 636076274162565607 Dernière heure d'écriture: 636076274162655722
Professeur de programmation

1

Si ce n'est pas demandé, c'est dommage qu'il n'y ait pas d'échantillons de solution prêts pour F #. Pour résoudre ce problème, voici ma recette, juste parce que je le peux et F # est un merveilleux langage .NET.

Les événements dupliqués sont filtrés à l'aide du FSharp.Control.Reactivepackage, qui n'est qu'un wrapper F # pour les extensions réactives. Tout cela peut être ciblé sur un cadre complet ou 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

Dans mon cas, j'ai besoin d'obtenir la dernière ligne d'un fichier texte inséré par une autre application, dès que l'insertion est terminée. Voici ma solution. Lorsque le premier événement est déclenché, je désactive l'observateur de lever d'autres, puis j'appelle la minuterie TimeElapsedEvent parce que lorsque ma fonction de poignée OnChanged est appelée, j'ai besoin de la taille du fichier texte, mais la taille à ce moment-là n'est pas la taille réelle, c'est la taille du fichier immédiatement avant l'insertion. J'attends donc un moment pour continuer avec la bonne taille de fichier.

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

Essayez ça, ça marche bien

  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

Je voulais réagir uniquement sur le dernier événement, juste au cas où, également sur un changement de fichier linux, il semblait que le fichier était vide au premier appel, puis rempli à nouveau le suivant et cela ne me dérangeait pas de perdre du temps au cas où le système d'exploitation a décidé de faire un changement de fichier / attribut.

J'utilise .NET async ici pour m'aider à effectuer le filetage.

    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

Je pense que la meilleure solution pour résoudre le problème est d'utiliser des extensions réactives Lorsque vous transformez un événement en observable, vous pouvez simplement ajouter Throttling (..) (initialement appelé Debounce (..))

Exemple de code ici

        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

J'ai pu le faire en ajoutant une fonction qui vérifie les doublons dans un tableau de tampons.

Ensuite, effectuez l'action après que le tableau n'a pas été modifié pour le temps X à l'aide d'une minuterie: - Réinitialisez la minuterie à chaque fois que quelque chose est écrit dans le tampon - Effectuez une action sur tick

Cela intercepte également un autre type de duplication. Si vous modifiez un fichier dans un dossier, le dossier déclenche également un événement Change.

Function is_duplicate(str1 As String) As Boolean
    If lb_actions_list.Items.Count = 0 Then
        Return False
    Else
        Dim compStr As String = lb_actions_list.Items(lb_actions_list.Items.Count - 1).ToString
        compStr = compStr.Substring(compStr.IndexOf("-") + 1).Trim

        If compStr <> str1 AndAlso compStr.parentDir <> str1 & "\" Then
            Return False
        Else
            Return True
        End If
    End If
End Function

Public Module extentions
<Extension()>
Public Function parentDir(ByVal aString As String) As String
    Return aString.Substring(0, CInt(InStrRev(aString, "\", aString.Length - 1)))
End Function
End Module

0

Cette solution a fonctionné pour moi sur l'application de production:

Environnement:

VB.Net Framework 4.5.2

Définissez manuellement les propriétés de l'objet: NotifyFilter = Size

Utilisez ensuite ce 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

Il utilise le même concept que @Jamie Krcmar mais pour VB.NET
wpcoder

0

Essaye ça!

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

J'ai dû combiner plusieurs idées des articles ci-dessus et ajouter une vérification de verrouillage de fichier pour que cela fonctionne pour moi:

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

J'ai abordé le problème de double création comme celui-ci, qui ignore le premier événement:

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
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.