.Net : le composant FileSystemWatcher

Ronald VASSEUR

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.

- Titre : .Net : le composant FileSystemWatcher

- Auteur : Ronald VASSEUR

- Parution : 9 août 2006

- Mise à jour : 27 février 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2006 Ronald Vasseur. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction

- I. Utiliser le composant FileSystemWatcher

- I-A. Avec le designer de Visual Studio

- I-B. Par le code

- II. Récupération des évènements survenus

- II-A. Cas général

- II-B. Utilisation avec le Framework .Net 2.0 et Windows Form

- III. Exemples d'utilisation d'un FileSystemWatcher

- III-A. Initialisation du FileSystemWatcher

- III-B. Scruter la création de fichiers

- III-C. Scruter l'effacement de fichiers

- III-D. Scruter un changement effectué sur un fichier

- III-E. Scruter le renommage de fichiers

- III-F. Une petite remarque sur la gestion d'évènements

- IV. Les limitations de ce composant

- IV-A. Les risques de Buffer OverFlow

- IV-B. 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érations sur un fichier ou répertoire, ou, sur leurs attributs.

Ce composant n'est pas spécifique aux applications Windows Forms et peut être indifféremment employé dans vos projets ASP.Net ou console.

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

I-A - 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 à outils. 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 :

I-B - 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é « New ». Voici un exemple, attention cela va être très bref :

Tout d'abord ne pas oublier de faire l'Import du System.IO :

Imports 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'il 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, quelle que soit la méthode que vous avez utilisée (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 reprend tout cela :

Initialisation du FileSystemWatcher

' Initialisation du FileSystemWatcher
Private Sub initFsWatcher()

' Répertoire à surveiller
myFsWatcher.Path = Me.folderToWatch

' Surveille les sous-répertoires
myFsWatcher.IncludeSubdirectories = True

' Surveille tous les fichiers sans exception
myFsWatcher.Filter = "*.*"

' Surveille les types d'évènements suivants
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)

' Permet de faire s'exécuter tous les EventHandlers dans le thread principal
myFsWatcher.SynchronizingObject = Me

' Active la levée d'évènements par le FileSystemWatcher
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.

II - Récupération des évènements survenus

II-A - Cas général

Pour récupérer les évènements, il suffit 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

' Événements : changement
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.

II-B - 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 dysfonctionnements. 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 résoudre 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.

- Utiliser des 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.

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éé par le designer de Visual Studio 2005, en effet dans ce dernier cas, par défaut la propriété SynchronizingObject est positionnée à « True ».

III - 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é.

III-A - Initialisation du FileSystemWatcher

Le code ci-dessous permet d'initialiser 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

' Déclaration du FileSystemWatcher
Private WithEvents myFsWatcher As New FileSystemWatcher

' Contient le chemin complet du dossier qui va être surveillé
Private folderToWatch As String = "c:\rep_test\"

' Initialisation du FileSystemWatcher
Private Sub initFsWatcher()

' Répertoire à surveiller
myFsWatcher.Path = Me.folderToWatch

' Surveille les sous-répertoires
myFsWatcher.IncludeSubdirectories = True

' Surveille tous les fichiers sans exception
myFsWatcher.Filter = "*.*"

' Surveille les types d'évènements suivants
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)

' Permet de faire s'exécuter tous les EventHandlers dans le thread principal
myFsWatcher.SynchronizingObject = Me

' Active la levée d'évènements par le FileSystemWatcher
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.

III-B - 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

' Évènement : création
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

III-C - Scruter l'effacement de fichiers

Voici le code permettant de surveiller tout effacement dans le répertoire observé :

Effacement d'un fichier

' Événement : effacement
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

III-D - 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

' Événement : changement
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

III-E - Scruter le renommage de fichiers

Voici le code permettant de surveiller tout renommage d'un fichier dans le répertoire observé :

Renommage d'un fichier

' Événement : renommage
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

III-F - Une petite remarque sur la gestion d'évènements

Plutôt que d'ajouter Handles myFsWatcher.« event » une solution aurait été de rajouter dans la fonction d'initialisation du FileSystemWatcher quatre EventHandlers qui auraient dirigé les quatre types d'évènements 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.

IV - 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 !

IV-A - 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émoire, 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.

IV-B - 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éfléchissant à 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 susceptible de plier à la moindre charge un peu forte.

Ressources

La classe FileSystemWatcher
La solution Visual Studio 2005 (serveur principal)
La solution Visual Studio 2005 (serveur secondaire)
Application exemple (serveur principal)
Application exemple (serveur secondaire)

Mon blog
Mes articles
Les articles des autres rédacteurs
La rubrique .Net de Developpez.com
La rubrique Sécurité de Developpez.com

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.