.Net : le composant FileSystemWatcherDate de publication : 09/08/2006
Par
Ronald VASSEUR (autres articles)
La classe FileSystemWatcher fait partie du namespace System.IO, elle possède des méthodes et
des propriétés qui nous permettent de surveiller les opérations effectuées dans un répertoire, ou dans une
arborescence de répertoires. Il est alors possible de scruter des évènements se produisant
sur tout ou partie de ce sous ensemble de répertoires.
Introduction
1. Utiliser le composant FileSystemWatcher
1.1. Avec le designer de Visual Studio
1.2. Par le code
2. Récupération des évènements survenus
2.1. Cas général
2.2. Utilisation avec le Framework .Net 2.0 et Windows Form
3. Exemples d'utilisation d'un FileSystemWatcher
3.1. Initialisation du FileSystemWatcher
3.2. Scruter la création de fichiers
3.3. Scruter l'effacement de fichiers
3.4. Scruter un changement effectué sur un fichier
3.5. Scruter le renommage de fichiers
3.6. Une petite remarque sur la gestion d'évènements
4. Les limitations de ce composant
4.1. Les risques de Buffer OverFlow
4.2. Problème de gestion de la charge
Ressources
Conclusion
Introduction
La classe FileSystemWatcher fait partie du namespace System.IO,
elle possède des méthodes et des propriétés qui nous permettent
de surveiller l'activité d'un répertoire, ou d'une arborescence
de répertoires. Il est alors possible de scruter des évènements
se produisant sur tout ou partie de ce sous ensemble de répertoires.
Par évènement, je veux, par exemple, parler de modification
d'un fichier, d'accès en lecture à un répertoire, de création
d'un fichier… ou encore tout un ensemble d'opération sur un
fichier ou répertoire, ou, sur leurs attributs.
Ce composant n'est pas spécifiques aux applications Windows
Forms et peut être indifféremment employé dans vos projets
ASP.Net ou console.
1. Utiliser le composant FileSystemWatcher
Il existe deux manières pour utiliser un composant FileSystemWatcher,
soit entièrement par le code, soit en utilisant le designer de Visual Studio.
1.1. Avec le designer de Visual Studio
Avec Visual Studio, l'initialisation d'un composant FileSystemWatcher
se limite à un simple "glisser / déposer" depuis la boite à outil.
Dès lors un composant est ajouté en dessous de votre Form courant
dans le designer. Ses propriétés, comme tout autre "composant visuel"
sont accessibles dans la zone de propriétés, faire F4 si jamais celle-ci
n'est pas visible. Voici une capture d'écran de ces propriétés :
1.2. Par le code
Pour ceux n'utilisant pas Visual Studio, ou n'aimant pas trop
user du glisser / déposer de composants, vous pouvez bien évidemment
initialiser entièrement votre composant par le code. Comme n'importe
quel autre objet, vous devez l'initialiser à l'aide du mot clés "New".
Voici un exemple, attention cela va être très bref :
Tout d'abord ne pas oublier de faire l'Imports du System.IO :
Pour l'instanciation il n'y a rien d'affolant...
Dim WithEvents myFsWatcher As New FileSystemWatcher |
Une fois que vous avez initialisé ce composant, que ce soit
par le designer ou par le code, il n'est cependant pas utilisable
en l'état. Il faut au minimum renseigner les propriétés suivantes :
Path : il s'agit du chemin complet du répertoire à surveiller.
IncludeSubdirectories : booléen qui détermine s'il faut
surveiller seulement (False) le répertoire désigné par la propriété
Path, ou en plus les sous répertoires qu'ils contient, (True).
Filter : cette propriété vous permet de surveiller seulement
un type de fichier en fonction de son extension (*.doc par exemple),
ou alors tous les fichiers avec la valeur *.*, il s'agit d'ailleurs
de la valeur par défaut.
NotifyFilter : cette propriété contient en fait l'ensemble des
propriétés qui pourront être surveillées par le FileSystemWatcher,
en voici une liste exhaustive :
- Attributes : The attributes of the file or folder.
- CreationTime : The time the file or folder was created.
- DirectoryName : The name of the directory.
- FileName : The name of the file.
- LastAccess : The date the file or folder was last opened.
- LastWrite : The date the file or folder last had anything written to it.
- Security : The security settings of the file or folder.
- Size : The size of the file or folder.
Si vous voulez lister plusieurs types de paramètres à surveiller, ce
qui représente la majorité des cas, vous devez simplement ajouter
l'opérateur logique Or entre chaque paramètre.
En fonction de la méthode que vous avez choisie pour placer
un composant FileSystemWatcher dans votre application vous
devrez utiliser soit la fenêtre de propriété de Visual Studio,
soit saisir ces propriétés à la main. Comme pour tout
autre composant, quelque soit la méthode que vous avez utilisé
(par le designer de Visual Studio ou par le code) vous pouvez
saisir toutes ces propriétés dans le code.
Maintenant que nous avons étudié comment initialiser correctement
le FileSystemWatcher, nous allons voir un petit exemple pratique
qui reprends tout cela :
| Initialisation du FileSystemWatcher |
Private Sub initFsWatcher()
myFsWatcher.Path = Me.folderToWatch
myFsWatcher.IncludeSubdirectories = True
myFsWatcher.Filter = "*.*"
myFsWatcher.NotifyFilter = (NotifyFilters.Attributes _
Or NotifyFilters.CreationTime _
Or NotifyFilters.DirectoryName _
Or NotifyFilters.FileName _
Or NotifyFilters.LastAccess _
Or NotifyFilters.LastWrite _
Or NotifyFilters.Security _
Or NotifyFilters.Size)
myFsWatcher.SynchronizingObject = Me
myFsWatcher.EnableRaisingEvents = True
End Sub |
Votre composant émet une notification dès qu'un évènement
répond aux critères précédemment définis. Nous allons donc maintenant
voir comment récupérer ces évènements.
2. Récupération des évènements survenus
2.1. Cas général
Pour récupérer les évènements, il suffit simplement d'utiliser
des EventHandlers. Vous allez voir que c'est d'une très grande
simplicité. Ces EventHandlers sont en fait des méthodes qui
vont être exécutées lorsque survient un évènement du type
dont elles sont chargées d'assurer la surveillance.
Un bon exemple, valant mieux qu'une mauvaise explication,
voici concrètement à quoi cela ressemble :
| Gestion d'un évènement |
Private Sub myFsWatcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs) _
Handles myFsWatcher.Changed
Me.rtbEvents.AppendText("Un changement vient de se produire sur un fichier : _
" & e.Name & ControlChars.CrLf)
End Sub |
Vous voyez que nos EventHandlers sont chargés de récupérer un
type d'évènement précis et d'ensuite effectuer la tâche que
l'on souhaite : cela peut être l'affichage d'une information
à l'écran, l'ajout d'un enregistrement dans un journal, ou
encore la compression ou l'encodage du fichier ayant généré
cet évènement... Vous l'aurez compris les possibilités sont
en fait infinies et seront dictées par les besoins de votre application."
Après avoir vu le cas général, où tout se déroule pour le
mieux dans le meilleur des mondes, voici maintenant une exception à la règle.
2.2. Utilisation avec le Framework .Net 2.0 et Windows Form
Si vous utilisez Visual Studio 2005 ou du moins le Framework .Net
2.0 vous allez être confronté à un problème, de par la conception
des contrôles Windows Form et du Framework .Net 2.0. si vous
souhaitez répercuter un évènement (de type Created,
Changed, Renamed ou Deleted) émis par le FileSystemWatcher. En effet,
les EventHandlers associés à ces évènements s'exécutent dans un thread
différent (issu du pool de thread) du thread principal de votre
application. Voilà d'ailleurs comment le MSDN nous explique cela :
L'accès aux contrôles Windows Form n'est pas fondamentalement
thread-safe. Si vous avez deux ou plusieurs threads qui manipulent
l'état d'un contrôle, il est possible de forcer le contrôle dans un
état incohérent. D'autres bogues en rapport avec les threads sont
possibles aussi, y compris les conditions de concurrence critique
et de blocages. Il est important de faire en sorte que l'accès
à vos contrôles s'effectue de manière thread-safe.
En clair, lorsqu'un autre thread, que le thread principal,
essaye d'accéder à des méthodes ou des propriétés d'un contrôle
il peut en résulter des comportements aléatoires de celui-ci
et un certain nombre de disfonctionnements. Pour éviter cela,
depuis la version 2.0 du Framework une vérification est
effectuée et une exception levée, dans le cas où votre
application se trouve dans ce cas de figure.
Seuls quatre "EventHandlers" ne sont pas exécutés dans le même
thread que la Windows Forms; ils concernent les Events suivants :
- Created
- Deleted
- Renamed
- Changed
Il existe trois moyens principaux de solutionner cela :
- Désactiver la vérification effectuée lors de l'exécution :
Les vérifications effectuées par le Framework peuvent
être désactivées en ajoutant la ligne de code suivant à
votre application :
| Désactivation du contrôle | System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False |
On se retrouve ainsi dans la situation du Framework .Net 1.1
qui n'effectue pas de contrôles à ce niveau là. Cependant,
pour des raisons de fiabilité et de robustesse de votre application,
cela n'est pas recommandé, vous vous exposez à des bugs relativement complexes.
La solution "idéale" en l'état est d'utiliser les délégués.
Je ne vais pas entrer dans le détail des délégués, mais sachez
uniquement que nous allons les utiliser pour faire un appel
asynchrone d'une méthode qui va être chargée d'afficher les
informations souhaitées issues de l'évènement (Changed,
Created, Deleted ou Renamed).
- Utilisation de la propriété SynchronizingObject :
Cette propriété permet de vous assurer que les gestionnaires des
quatre évènements qui peuvent poser problème seront exécutés
dans le même thread que celui de la Form utilisant le
FileSystemWatcher. Ainsi plus besoin d'utiliser les délégués
qui rajoutent de la complexité là où ce n'est pas vraiment
indispensable.
Voici comment positionner cette propriété pour que la
synchronisation des EventHandlers soit réalisée avec la
Form courante :
| SynchronizingObject | myFsWatcher.SynchronizingObject = Me |
 |
Remarque : je vous rappelle que ces "problèmes" d'InvalidOperationException
ne se présentent que lorsque vous utilisez le Framework 2.0
et que votre composant FileSystemWatcher n'a pas été crée
par le designer de Visual Studio 2005, en effet dans ce
dernier cas, par défaut la propriété SynchronizingObject
est positionnée à "Me" (la Form courante).
|
3. Exemples d'utilisation d'un FileSystemWatcher
Ci-dessous des morceaux de code développé avec
Visual Studio 2005, permettant d'utiliser un FileSystemWatcher
pour "observer" des évènements prédéfinis sur un répertoire
donné :
3.1. Initialisation du FileSystemWatcher
Le code ci dessous permet d'initaliser le FileSystemWatcher, en
l'état il va surveiller l'activité sur le répertoire "c:\rep_test\"
pour tous les fichiers s'y trouvant ainsi que dans ses sous-répertoires.
| Initialisation du FileSystemWatcher |
Private WithEvents myFsWatcher As New FileSystemWatcher
Private folderToWatch As String = "c:\rep_test\"
Private Sub initFsWatcher()
myFsWatcher.Path = Me.folderToWatch
myFsWatcher.IncludeSubdirectories = True
myFsWatcher.Filter = "*.*"
myFsWatcher.NotifyFilter = (NotifyFilters.Attributes _
Or NotifyFilters.CreationTime _
Or NotifyFilters.DirectoryName _
Or NotifyFilters.FileName _
Or NotifyFilters.LastAccess _
Or NotifyFilters.LastWrite _
Or NotifyFilters.Security _
Or NotifyFilters.Size)
myFsWatcher.SynchronizingObject = Me
myFsWatcher.EnableRaisingEvents = True
End Sub |
Maintenant que notre FileSystemWatcher est opérationnel, il faut récupérer et
traiter les évènements qui sont émis. Pour cela nous allons utiliser le code suivant.
3.2. Scruter la création de fichiers
Voici le code permettant de surveiller toute création dans le répertoire observé :
| Création d'un fichier |
Private Sub myFsWatcher_Created(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles myFsWatcher.Created
Me.rtbEvents.AppendText("Un fichier vient d'être créé : " & e.Name & ControlChars.CrLf)
End Sub |
3.3. Scruter l'effacement de fichiers
Voici le code permettant de surveiller tout effacement dans le répertoire observé :
| Effacement d'un fichier |
Private Sub myFsWatcher_Deleted(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles myFsWatcher.Deleted
Me.rtbEvents.AppendText("Un fichier vient d'être effacé : " & e.Name & ControlChars.CrLf)
End Sub |
3.4. Scruter un changement effectué sur un fichier
Voici le code permettant de surveiller toute modification sur un fichier dans le répertoire observé :
| Changement sur un fichier |
Private Sub myFsWatcher_Changed(ByVal sender As Object, ByVal e As FileSystemEventArgs) Handles myFsWatcher.Changed
Me.rtbEvents.AppendText("Un changement vient de se produire sur un fichier : " & e.Name & ControlChars.CrLf)
End Sub |
3.5. Scruter le renommage de fichiers
Voici le code permettant de surveiller tout renommage d'un fichier dans le répertoire obeservé :
| Renommage d'un fichier |
Private Sub myFsWatcher_Renamed(ByVal sender As Object, ByVal e As System.IO.RenamedEventArgs) Handles myFsWatcher.Renamed
Me.rtbEvents.AppendText("Un fichier vient d'être renommé : " & e.OldName & " en " & e.Name & ControlChars.CrLf)
End Sub |
3.6. Une petite remarque sur la gestion d'évènements
Plutôt que d'ajouter Handles myFsWatcher."event" comme dans le paragraphe précédent, une solution aurait été de
rajouter dans la fonction d'initialisation du FileSystemWatcher 4 EventHandlers
qui aurait dirigé les quatre types d'évèments vers les bonnes fonctions de la
manière suivante :
| Ajout des gestionnaires d'évènements | AddHandler myFsWatcher.Changed, AddressOf myFsWatcher_Changed
AddHandler myFsWatcher.Created, AddressOf myFsWatcher_Created
AddHandler myFsWatcher.Deleted, AddressOf myFsWatcher_Deleted
AddHandler myFsWatcher.Renamed, AddressOf myFsWatcher_Renamed |
Cela aurait donc imposé de supprimer le Handles myFsWatcher."event" après la signature des
quatre fonctions de gestion des évènements.
4. Les limitations de ce composant
Bien que ce composant soit vraiment d'une très grande utilité
dans de nombreuses situations, et permette d'économiser beaucoup
de temps de développement, il possède quand même quelques
limitations qui peuvent s'avérer gênantes... Mais rassurez-vous
il existe des moyens simples pour les contourner !
4.1. Les risques de Buffer OverFlow
Un risque d'erreur non négligeable existe... au cas où un trop grand nombre
d'évènements surviendraient en même temps, et au cas où les ressources, notamment mémoires, de la machine seraient
insuffisantes, il y a un risque non négligeable de buffer overflow ! Dans ce cas des
évènements pourraient être perdus et non notifiés. Une parade existe, il faut augmenter
la taille du buffer grâce à la propriété InternalBufferSize, pour ainsi prévenir des soucis
de Buffer Overlfow.
4.2. Problème de gestion de la charge
Une autre difficulté que l'on peut rencontrer avec le FileSystemWatcher est le fait que
si le nombre d'évènements à gérer est trop grand, alors très rapidement va se poser un
problème de gestion de la charge, cela peut être par exemple le cas sur des serveurs de fichiers,
ainsi il faut être prudent dans l'utilisation des FSW. Il faut savoir cibler les fichiers et évènements
nécessaires, faute de quoi la machine faisant tourner le programme va devoir assumer
des charges élevées, qui vont avoir un impact fort sur les performances. Il faut donc savoir utiliser
les FSW avec modération et en réflechissant à la charge potentielle à supporter.
Cependant, il faut quand même garder à l'esprit que le FileSystemWatcher est très performant
et consomme peu de ressources, il n'est donc pas succeptible de plier à la moindre
charge un peu forte.
Ressources
Conclusion
Comme vous avez pu le constater tout au long de cet article, du moins
je le souhaite, FileSystemWatcher est un composant puissant, mais
surtout adaptable à de nombreuses situations. Grâce à ce composant
vos applications pourront aisément surveiller les opérations
effectuées sur un système de fichiers. Il faut cependant garder à l'esprit
que même si ce composant est puissant, et relativement robuste il faut
l'employer de manière raisonnable pour ne pas surcharger sa machine. En espérant que cet
article vous aura aidé à comprendre le FileSystemWatcher, je vous donne rendez-vous pour
un prochain article.
Un grand merci à Khany pour la relecture de cet article, ainsi
qu'à l'équipe Dotnet pour son aide.
 
|