2010-04-20 5 views
17

L'une des choses qui m'a longtemps dérangé à propos de FileSystemWatcher est la façon dont il déclenche plusieurs événements pour une seule modification logique d'un fichier. Je sais pourquoi cela arrive, mais je ne veux pas avoir à m'en soucier - je veux juste refaire le fichier une fois, pas 4-6 fois de suite. Idéalement, il y aurait un événement qui ne se déclencherait que lorsqu'un fichier donné est modifié, plutôt qu'à chaque étape du processus.Extensions réactives par rapport à FileSystemWatcher

Au fil des ans, j'ai trouvé diverses solutions à ce problème, de divers degrés de laideur. Je pensais que Reactive Extensions serait la solution ultime, mais il y a quelque chose que je ne fais pas bien, et j'espère que quelqu'un peut signaler mon erreur.

J'ai une méthode d'extension:

public static IObservable<IEvent<FileSystemEventArgs>> GetChanged(this FileSystemWatcher that) 
{ 
    return Observable.FromEvent<FileSystemEventArgs>(that, "Changed"); 
} 

En fin de compte, je voudrais obtenir un événement par nom de fichier, dans une période donnée - de sorte que quatre événements dans une ligne avec un nom de fichier unique sont réduits à un événement, mais je ne perds rien si plusieurs fichiers sont modifiés en même temps. BufferWithTime sonne comme la solution idéale.

var bufferedChange = watcher.GetChanged() 
    .Select(e => e.EventArgs.FullPath) 
    .BufferWithTime(TimeSpan.FromSeconds(1)) 
    .Where(e => e.Count > 0) 
    .Select(e => e.Distinct()); 

Quand je souscris à cette observable, un seul changement dans un fichier surveillé déclenche ma méthode d'abonnement quatre fois de suite, ce qui va à l'encontre plutôt le but. Si je supprime l'appel Distinct(), je vois que chacun des quatre appels contient deux événements identiques - il y a donc une mise en mémoire tampon. Augmenter le TimeSpan passé à BufferWithTime semble n'avoir aucun effet - je suis allé aussi haut que 20 secondes sans aucun changement de comportement.

Ceci est ma première incursion dans Rx, donc il me manque probablement quelque chose d'évident. Est-ce que je le fais mal? Est-ce qu'il y a une meilleure approche? Merci pour vos suggestions ...

+1

Pourriez-vous conclure dans un programme complet? Je serais intéressé à enquêter ... –

+0

Oui, je vais faire un cas de test isolé. Maintenant que j'y pense, j'ai plus d'un observateur manipulant plus d'un dossier, et je dois prouver que ce n'est pas quatre observateurs différents qui reçoivent la même paire d'événements d'une façon ou d'une autre. –

Répondre

3

Mon erreur. D'une manière ou d'une autre, plusieurs FileSystemWatchers surveillent les dossiers de l'autre. L'observable se déclenchait une fois pour chaque observateur, mais BufferWithTime semble fonctionner correctement. J'ai encore besoin de comprendre pourquoi mes observateurs lancent des événements pour des dossiers que je pensais qu'ils étaient configurés pour ignorer, mais cela n'a rien à voir avec Rx ou cette question.

En fait, je peux peut-être sur ce problème punt et passer d'avoir un observateur unique suivi d'un dossier parent, en utilisant Rx pour filtrer les événements de dossiers que je ne suis pas intéressé par.

+0

Fonctionne très bien. Moins de veilleurs est mieux. Je commence à vraiment aimer Rx. –

3

BufferWithTime.Where () .Select (...) va faire le travail, mais ce que vous voulez vraiment est Throttle()

+0

J'ai regardé 'Throttle()', mais je n'étais pas sûr que cela fonctionnerait dans ce cas. Disons que je reçois 12 événements avec trois noms de fichiers dans la même seconde - puis-je être certain que Throttle laissera passer les trois de ces douze événements? [La documentation] (http://goo.gl/rzg2) n'aide pas beaucoup. –

+0

Je suppose que si je sélectionne le nom de fichier qui m'intéresse avant la limitation, je n'ai pas à m'inquiéter de la façon dont différentes instances de 'IEvent ' implémentent l'égalité, ce qui était mon principal problème avec Throttle. –

+0

Ahh - J'ai mal compris –

9

juste pour réchauffer un vieux sujet, que je travaille sur ce moment aussi:

Bien sûr ce sujet est négligeable dans le contexte de l'observation d'un fichier, car FileSystemWatcher se déclenche toutes les ~ 3 secondes avec un événement Changed pour un seul fichier lorsque vous suivez Taille via

_fileSystemWatcher.NotifyFilter = NotifyFilters.Size | .... 

Mais supposons que FileSystemWatcher tireraient beaucoup d'événements dans une rangée (peut-être beaucoup de fichiers sont modifiés/renommé/créé), et d'autres personnes lire ceci:

Vous ne voulez pas utilisez Throttle ou BufferWithTime dans ce cas: Throttle est un peu trompeur .. il interdit tout tir jusqu'à ce que TimeSpan temps est écoulé sans un événement. Signification: il ne pourrait jamais tirer quand vous utilisez quelque chose comme Throttle(TimeSpan.FromMilliseconds(200)), et après chaque événement il y a une pause < 200 ms. Donc, ce n'est pas vraiment le "étranglement" que les gens attendent.C'est bon pour l'entrée de l'utilisateur, lorsque vous voulez attendre que l'utilisateur a cessé de taper quelque chose. C'est mauvais pour la limitation de la charge.

BufferWithTime n'est pas non plus ce que vous voulez: il suffit de remplir un timebuffer. Bon lorsque vous avez une charge initiale élevée par événement, comme l'ouverture d'une connexion à un service Web. Dans ce cas, vous souhaitez traiter les événements par lots toutes les secondes. Mais pas quand loadbalancing, car le nombre d'événements ne change pas.

La solution est la méthode Sample(TimeSpan time): elle prend le dernier événement dans un TimeSpan, qui est le "vrai" Throttle. Je pense que les gars de Rx ont vraiment foiré le nom dans ce cas.

+0

Merci de réchauffer cela, j'avais gardé le code d'échantillon à portée de main pour ce problème et pris la peine de regarder et j'ai eu .Sample dans mon code. Je voudrais savoir si regarder la taille du fichier garantit que vous obtiendrez un événement dans le cas où le fichier est modifié sans modifier sa taille? –

+1

@DavidGrenier l'a essayé, il ne se déclenche que sur NotifyFilter.LastWrite dans ce cas. Mais cela fait partie du filtre standard, qui est LastWrite | NomFichier | DirectoryName, vous devez donc ajouter le "watch for size change" manuellement. Donc j'utilise au moins NF.LastWrite | NF.Size – hko

4

vous pouvez utiliser un groupe pour agréger les événements de système de fichiers par nom de fichier et utiliser l'observable résultant avec la méthode des extensions de Throttle. J'ai écrit un petit échantillon en utilisant des entiers, mais l'idée de base est la même.

var obs = from n in Enumerable.Range(1, 40).ToObservable() 
    group n by n/10 into g 
    select new { g.Key, Obs = g.Throttle(TimeSpan.FromMilliseconds(10.0)) } into h 
    from x in h.Obs 
    select x; 
obs.Subscribe(x => Console.WriteLine(x)); 

sorties:

9 
19 
29 
39 
40 

qui est pour chaque groupe (n/10) le dernier nombre entier observée.

Questions connexes