Guide pratique de FileSystemWatcher - Gérer les événements manqués et les doublons
· Mis à jour le: · Go Komura · FileSystemWatcher, C#, .NET, Développement Windows, Intégration par fichiers, Conception
FileSystemWatcher est la première API à laquelle on pense pour surveiller les changements de fichiers dans .NET sous Windows. Elle permet de recevoir sous forme d’événements les créations, modifications, suppressions et renommages de fichiers et de répertoires, ce qui est pratique - mais si l’on traite Created ou Changed comme des notifications d’achèvement, on se fait assez couramment piéger par des événements manqués, des notifications en double et la lecture de fichiers à moitié écrits.
Cet article fait le point sur l’utilisation de FileSystemWatcher et ses pièges, en se plaçant principalement dans le contexte d’une intégration par fichiers en .NET sous Windows. Pour les notions sous-jacentes de contrôle d’exclusion, voir aussi Les bases du contrôle d’exclusion pour l’intégration par fichiers - bonnes pratiques de verrouillage de fichiers et de claim atomique.
En pratique, Created peut se déclencher alors que le fichier est encore en cours de copie, et rien ne garantit que Changed ne se déclenche qu’une seule fois. Quand les changements se concentrent sur une courte période, le buffer interne peut déborder et faire perdre certains changements individuels.
Le cœur de la conception tient donc en ceci.
- Les notifications sont des déclencheurs
- La vérité se trouve dans une réanalyse du répertoire
- La propriété s’obtient par un claim atomique
- L’idempotence rattrape ce qui reste
Dans le corps de l’article, nous passons en revue, avec cet état d’esprit, les pièges qui surviennent lorsqu’on intègre FileSystemWatcher dans un dispositif d’intégration par fichiers.
Le code présenté dans cet article est publié sur GitHub sous la forme d’un ensemble complet, compilable et exécutable (une bibliothèque, une démo console fonctionnant sur un répertoire temporaire, et des tests unitaires qui créent et modifient réellement des fichiers pour vérifier les événements).
filesystemwatcher-safe-basics - komurasoft-blog-samples (GitHub)
Table des matières
- La conclusion d’abord (en une phrase)
- Schémas d’erreur courants avec
FileSystemWatcher(diagrammes)- 2.1. Prendre
Createdpour une notification d’achèvement - 2.2. Se fier au nombre et à l’ordre des
Changed - 2.3. Perdre des changements à cause d’un débordement du buffer interne
- 2.1. Prendre
- Anti-patterns
- 3.1. Traiter directement dans le gestionnaire d’événement
- 3.2. Essayer de reconstruire l’état réel à partir de la séquence d’événements
- 3.3. Considérer l’arrêt des
Changedcomme un achèvement - 3.4. Croire qu’augmenter
InternalBufferSizerègle le problème - 3.5. Se contenter de journaliser
Errorsans réagir
- Bonnes pratiques
- 4.1. Replier les notifications en « demandes de réanalyse »
- 4.2. Rendre l’achèvement explicite côté émetteur
- 4.3. Le récepteur prend un claim de façon atomique
- 4.4. Réanalyse complète au démarrage / en cas d’overflow / lors d’une reconnexion
- 4.5. Partir du principe de l’idempotence
- Pseudocode (extraits)
- 5.1. Le schéma d’échec typique
- 5.2. Un exemple dans la bonne direction (esquissé rapidement)
- Comment choisir, à grands traits
- Conclusion
- Références
1. La conclusion d’abord (en une phrase)
- Les événements de
FileSystemWatcherne sont pas des notifications d’achèvement, mais des signes de changement Created/Changed/Renamedpeuvent se dupliquer, arriver dans un ordre différent de ce qu’on imagine, ou être perdus en cas d’overflow- Il est plus stable de ne pas faire de traitement lourd dans le gestionnaire d’événement, et de se contenter d’empiler des demandes de réanalyse
- L’achèvement doit en principe être rendu explicite via
temp -> close -> rename / replaceou viadone/ manifest - S’il y a plusieurs workers, il faut prendre le claim de façon atomique avant de lire
- Ajuster
InternalBufferSizen’est qu’un appoint. Au final, ce qui fonctionne, ce sont la réanalyse complète (full rescan) et l’idempotence
En résumé, il ne faut pas traiter FileSystemWatcher comme un « flux d’historique fiable ».
Les choses cassent bien moins souvent si l’on garde les notifications comme un simple signal du type « il est temps d’aller voir ».
2. Schémas d’erreur courants avec FileSystemWatcher (diagrammes)
2.1. Prendre Created pour une notification d’achèvement
C’est le piège le plus évident.
Lors d’une copie ou d’un transfert, Created se déclenche au moment même où le fichier est créé, et un ou plusieurs Changed peuvent suivre ensuite.
sequenceDiagram
participant Exp as Émetteur
participant Partage as watched dir
participant W as FileSystemWatcher
participant Rec as Récepteur
Exp->>Partage: Crée orders.csv
Partage-->>W: Created
W-->>Rec: OnCreated
Rec->>Partage: Ouvre et lit orders.csv
Note over Rec: Copie encore en cours
Exp->>Partage: Écrit le reste
Partage-->>W: Changed
Partage-->>W: Changed
Note over Rec: Lignes manquantes / JSON corrompu / ZIP corrompu
Created peut signifier que « le nom est devenu visible », mais cela ne garantit pas qu’« il est déjà possible de lire ».
Confondre les deux, c’est retomber sur le même piège que la section 2.1 de l’article précédent, simplement par un autre chemin.
2.2. Se fier au nombre et à l’ordre des Changed
Rien ne garantit que Changed ne se déclenche qu’une seule fois.
Même une opération ordinaire comme un déplacement ou un enregistrement peut apparaître découpée en plusieurs événements. Et l’on peut en plus capter les accès faits par un antivirus ou un indexeur.
sequenceDiagram
participant App as Appli qui enregistre
participant Dir as watched dir
participant AV as AV / indexeur
participant W as FileSystemWatcher
App->>Dir: Commence à enregistrer report.xlsx
Dir-->>W: Created
Dir-->>W: Changed
App->>Dir: Renomme depuis un fichier temporaire
Dir-->>W: Renamed
Dir-->>W: Changed
AV->>Dir: Scan / accès aux attributs
Dir-->>W: Changed
Note over W: Pas nécessairement une seule fois, ni dans cet ordre
Des attentes comme « un Changed suffit pour dire que c’est fini » ou « plus rien ne touche le fichier après Renamed » sont assez risquées.
Remarques complémentaires :
- Un rename de fichier peut déclencher un
Changed RenamedEventArgs.Namepeut êtrenullsi le système d’exploitation ne parvient pas à faire correspondre l’ancien et le nouveau nom- Les fichiers cachés (hidden file) ne sont pas non plus ignorés. Se dire « c’est un nom temp caché, donc il ne sera pas vu » ne tient pas
- Si l’on renomme le répertoire surveillé lui-même, ce changement n’est pas notifié
2.3. Perdre des changements à cause d’un débordement du buffer interne
FileSystemWatcher possède un buffer interne.
Quand les changements se concentrent sur une courte période, ce buffer déborde et des notifications individuelles sont perdues.
flowchart LR
A[Beaucoup de changements en peu de temps] --> B[Les notifications s'accumulent dans le buffer interne]
B --> C{Le traitement suit-il le rythme ?}
C -- Oui --> D[Traiter les événements un par un, dans l'ordre]
C -- Non --> E[Overflow]
E --> F[Événement Error]
F --> G[Ne plus faire confiance à l'intégrité de l'historique événement par événement]
G --> H[Réanalyse complète du répertoire]
Ce qui compte ici, c’est que « un overflow ne fait pas nécessairement perdre un seul événement ». L’intégrité même de la séquence d’événements individuels devient douteuse, il vaut donc mieux tout revérifier honnêtement.
3. Anti-patterns
3.1. Traiter directement dans le gestionnaire d’événement
Cela fait porter beaucoup trop de poids aux événements : la détection de l’achèvement et l’acquisition de la propriété.
watcher.Created += (_, e) =>
{
using var stream = File.OpenRead(e.FullPath);
Import(stream); // Peut-être encore en cours de copie
};
watcher.Error += (_, e) =>
{
Console.WriteLine(e.GetException()); // On se contente de l'afficher
};
Il y a deux problèmes.
- Au moment de
Created, le contenu peut encore être incomplet - Il n’y a aucune reprise en cas d’échec ou d’overflow
Le gestionnaire d’événement est à son meilleur quand il se contente de lever une demande de réanalyse et de revenir immédiatement. Si l’on commence ici des E/S lourdes ou des mises à jour de base de données, on se met soi-même en difficulté lors des rafales.
3.2. Essayer de reconstruire l’état réel à partir de la séquence d’événements
Une conception du type « ajouter dans un dictionnaire sur Created, mettre à jour sur Changed, supprimer sur Deleted, changer la clé sur Renamed » paraît élégante à première vue.
Mais dès que des doublons, des découpages, un overflow ou des perturbations externes s’en mêlent, la cohérence finit par se dégrader.
switch (e.ChangeType)
{
case WatcherChangeTypes.Created:
state[e.FullPath] = Pending;
break;
case WatcherChangeTypes.Changed:
state[e.FullPath] = Modified;
break;
case WatcherChangeTypes.Deleted:
state.Remove(e.FullPath);
break;
}
Plutôt que de s’acharner dans cette direction, il est plus robuste de revérifier à chaque fois ce qui existe réellement sur le disque. Car ce qui compte dans l’intégration par fichiers, c’est de trouver correctement ce qu’il est permis de traiter à cet instant précis, et non de reconstituer fidèlement l’historique des événements.
3.3. Considérer l’arrêt des Changed comme un achèvement
C’est une conception qui sent la même chose que le « la taille du fichier a arrêté de bouger, donc c’est fini » de l’article précédent. Cela paraît pratique, mais on décide de l’achèvement par supposition.
if (lastChangedAt + TimeSpan.FromSeconds(10) < DateTime.UtcNow)
{
return Ready;
}
Cela pose problème par exemple dans les cas suivants.
- La copie d’un gros fichier se met en pause en cours de route
- L’application émettrice enregistre en plusieurs étapes
- Sur un partage réseau, les notifications semblent arriver en retard
- Un processus externe réécrit ensuite des attributs ou des horodatages
L’achèvement est plus stable quand il est rendu explicite plutôt que déduit par supposition.
3.4. Croire qu’augmenter InternalBufferSize règle le problème
Ajuster InternalBufferSize est important, mais ce n’est pas le cœur de la conception.
- La valeur par défaut est
8192octets - Impossible de descendre sous
4096octets, et impossible de dépasser64 Ko - Le buffer utilise de la non-paged memory, donc l’agrandir n’est pas un geste aussi anodin qu’il y paraît
Autrement dit, même en le portant jusqu’à 64 Ko, dès qu’une rafale de notifications dépasse cette limite, c’est terminé.
Et cela ne résout pas d’un millimètre la question de savoir s’il s’agit ou non d’une notification d’achèvement.
Avant d’agrandir le buffer, il y a des choses à faire en premier.
- Restreindre la surveillance avec
Filter/Filters - Réduire
NotifyFilterau strict minimum nécessaire - Ne pas mettre
IncludeSubdirectoriesàtruesans réfléchir - Alléger les gestionnaires d’événements
- Mettre en place la réanalyse complète (full rescan) et l’idempotence
3.5. Se contenter de journaliser Error sans réagir
Error n’est pas le genre de notification qu’on peut « voir de temps en temps sans s’en soucier ».
C’est là que remontent les débordements de buffer (buffer overflow) et les situations où la poursuite de la surveillance échoue.
watcher.Error += (_, e) =>
{
_logger.LogError(e.GetException(), "watcher error");
// S'arrêter ici, c'est remarquer la perte sans jamais s'en remettre
};
Au minimum, voici jusqu’où il faut aller.
- Demander une réanalyse complète (full rescan)
- Si la poursuite de la surveillance semble compromise, envisager aussi de recréer le watcher
- Rendre le retraitement idempotent, en partant du principe que des événements ont pu être perdus
4. Bonnes pratiques
4.1. Replier les notifications en « demandes de réanalyse »
Câbler Created / Changed / Deleted / Renamed / Error chacun directement vers un traitement métier séparé rend les choses illisibles. Il faut d’abord tout replier en un seul type de signal : « va voir ».
flowchart LR
A[Created / Changed / Deleted / Renamed] --> Q[demande de réanalyse]
B[Error / overflow] --> Q
C[démarrage] --> Q
Q --> D[Réanalyser le répertoire]
D --> E[Énumérer les candidats prêts]
E --> F[Tenter un claim]
Points d’implémentation :
- Dans le gestionnaire d’événement, se contenter de mettre
dirty = trueet de lever un signal - Concentrer l’analyse sur un seul worker
- En cas de rafale, regrouper pendant environ 100 à 300 ms avant de lancer une seule analyse
- Si de nouvelles notifications arrivent pendant l’analyse, relancer une analyse une fois celle-ci terminée
Ainsi, que l’on reçoive 5 ou 50 événements, l’action finale reste unifiée : « regarder ce qui existe réellement et chercher ce qui est prêt ».
4.2. Rendre l’achèvement explicite côté émetteur
Si l’on maîtrise aussi le côté émetteur, il est plus efficace de corriger le protocole de publication plutôt que de s’acharner à détecter l’achèvement du côté de FileSystemWatcher.
La voie royale reste, sans surprise, celle-ci.
- Écrire tout le contenu sous un nom
temp - Faire un
close rename / replacesur le même système de fichiers- Si nécessaire, déposer un
done/ manifest en dernier
flowchart TD
A[Écrire tout le contenu dans data.tmp] --> B[flush / close]
B --> C[rename / replace vers data.csv]
C --> D[Déposer data.done / manifest.json]
D --> E[Le récepteur ne surveille que les noms finaux ou les fichiers done]
C’est la même chose que dans l’article précédent, mais c’est vraiment ce qui fait effet ici.
Il est plus juste de voir FileSystemWatcher non pas comme un outil qui invente l’achèvement, mais comme un outil qui repère plus tôt un achèvement explicitement déclaré.
4.3. Le récepteur prend un claim de façon atomique
Même quand une réanalyse trouve un candidat prêt, aller le lire directement permet à plusieurs workers de s’en saisir en même temps. Il faut donc prendre le claim de façon atomique avant de traiter.
sequenceDiagram
participant Scan as scanner
participant IN as incoming
participant P1 as processing/worker1
participant P2 as processing/worker2
Scan->>IN: Trouve order-123
Scan->>P1: rename order-123
Scan->>P2: rename order-123
Note over P1,P2: Seul celui qui réussit en premier obtient la propriété
Comme mentionné dans l’article précédent, le rename incoming -> processing/<worker>/ est le plus clair.
Regrouper en particulier le corps du fichier + le manifest + les fichiers auxiliaires dans un seul répertoire est particulièrement pratique, car cela permet ensuite de faire le claim par bundle.
incoming/
order-123/
payload.csv
manifest.json
Ainsi, un seul rename du répertoire du bundle suffit à obtenir la propriété.
4.4. Réanalyse complète au démarrage / en cas d’overflow / lors d’une reconnexion
Ceci est assez important.
- Les fichiers déjà présents avant le démarrage de l’application ne sont pas captés par les événements
- Une fois qu’un overflow s’est produit, la séquence d’événements individuels devient difficile à croire
- Quand un partage réseau ou une déconnexion temporaire entre en jeu, il est plus sûr de partir du principe qu’« un événement survenu entre-temps » a pu être manqué
Il vaut donc mieux prévoir une réanalyse complète au moins aux moments suivants.
- Au démarrage
- À la réception d’
Error - Juste après avoir recréé le watcher
- Périodiquement, à intervalle régulier, par précaution
La philosophie ici est la suivante : « le watcher donne des indices sur les différences, la réanalyse restaure la cohérence ».
4.5. Partir du principe de l’idempotence
Avec FileSystemWatcher, on finit par examiner plusieurs fois la même cible.
Ce n’est pas un bug ; il est plus stable de l’accepter comme un élément de conception.
Concrètement, cela donne à peu près ceci.
- Placer une
IdempotencyKeydans le manifest - Ne pas rejouer les effets de bord si le traitement a déjà eu lieu
- Rendre vérifiables les statuts « déjà archivé », « déjà enregistré en base » et « déjà envoyé »
- Faire en sorte qu’une réanalyse complète se limite à « revoir en toute sécurité la même chose une fois de plus »
Vouloir construire de l’exactly-once en s’appuyant uniquement sur des événements devient vite pénible. En pratique, il est plus solide d’accepter l’at-least-once et de refermer la boucle avec l’idempotence.
5. Pseudocode (extraits)
5.1. Le schéma d’échec typique
using var watcher = new FileSystemWatcher(incomingDir)
{
Filter = "*.csv",
IncludeSubdirectories = false,
EnableRaisingEvents = true,
InternalBufferSize = 64 * 1024
};
watcher.Created += (_, e) =>
{
// Suppose que Created = notification d'achèvement
ProcessFile(e.FullPath);
};
watcher.Changed += (_, e) =>
{
// Ça se déclenche plusieurs fois, alors on retraite à chaque fois
ProcessFile(e.FullPath);
};
watcher.Error += (_, e) =>
{
Console.WriteLine(e.GetException());
// Aucune reprise
};
Il y a quatre problèmes.
Created/Changedsont câblés directement vers le traitement métier- Il n’y a pas de détection de l’achèvement
- Pas de réanalyse complète en cas d’overflow
- Aucun mécanisme pour arrêter de traiter le même fichier à répétition
5.2. Un exemple dans la bonne direction (esquissé rapidement)
private readonly SemaphoreSlim _scanSignal = new(0, int.MaxValue);
private int _scanRequested = 0;
private int _fullRescanRequested = 0;
void OnAnyChange(object? sender, FileSystemEventArgs e)
{
RequestScan(full: false);
}
void OnRenamed(object? sender, RenamedEventArgs e)
{
RequestScan(full: false);
}
void OnError(object? sender, ErrorEventArgs e)
{
Log(e.GetException());
RequestScan(full: true);
}
void RequestScan(bool full)
{
if (full)
{
Interlocked.Exchange(ref _fullRescanRequested, 1);
}
if (Interlocked.Exchange(ref _scanRequested, 1) == 0)
{
_scanSignal.Release();
}
}
async Task ScannerLoopAsync(CancellationToken cancellationToken)
{
RequestScan(full: true); // analyse au démarrage
while (!cancellationToken.IsCancellationRequested)
{
await _scanSignal.WaitAsync(cancellationToken);
// Regrouper un peu les rafales de notifications
await Task.Delay(TimeSpan.FromMilliseconds(200), cancellationToken);
Interlocked.Exchange(ref _scanRequested, 0);
bool full = Interlocked.Exchange(ref _fullRescanRequested, 0) == 1;
foreach (var bundle in EnumerateReadyBundles(incomingDir, full))
{
var claimedPath = Path.Combine(processingDir, bundle.Name);
if (!TryClaimByRename(bundle.Path, claimedPath))
{
continue; // Un autre worker l'a déjà pris
}
var manifest = ReadManifest(Path.Combine(claimedPath, "manifest.json"));
if (AlreadyProcessed(manifest.IdempotencyKey))
{
MoveToArchive(claimedPath, archiveDir);
continue;
}
ProcessBundle(claimedPath);
RecordProcessed(manifest.IdempotencyKey);
MoveToArchive(claimedPath, archiveDir);
}
if (Volatile.Read(ref _scanRequested) == 1)
{
_scanSignal.Release(); // Ne pas perdre les notifications arrivées pendant l'analyse
}
}
}
Ce qui compte dans cet exemple, ce n’est pas le détail de l’API, mais le flux.
- Replier les notifications en demandes de réanalyse
- Trouver ce qui est prêt par l’analyse
- Prendre un claim
- Vérifier l’idempotence
- Traiter, enregistrer, puis déplacer vers l’archive
Les événements de FileSystemWatcher ne sont ici rien de plus qu’un trigger.
6. Comment choisir, à grands traits
-
Un seul worker récepteur / vous pouvez aussi corriger le côté émetteur Commencez par
temp -> close -> renameet une analyse au démarrage. Cela suffit déjà à obtenir une bonne stabilité. -
Plusieurs workers récepteurs En plus de ce qui précède, il vaut mieux ajouter le claim rename
incoming -> processing. -
Notifications fréquentes et nombreuses Restreignez
Filter/NotifyFilter/IncludeSubdirectorieset réduisez les gestionnaires d’événements au strict minimum. L’ajustement d’InternalBufferSizevient après. -
Les overflow posent problème / aucune perte n’est tolérable Basez-vous sur la réanalyse complète, et si cela reste insuffisant, il vaut mieux ne pas tout miser sur
FileSystemWatcherseul. Si vous êtes limité à Windows, le USN change journal est aussi une option. -
Vous ne contrôlez pas la manière dont le système en face écrit ses fichiers Plutôt que de combler par supposition les conditions d’achèvement, il est plus sûr d’examiner d’abord s’il est possible de négocier le protocole de publication. Si ce n’est pas possible, abaissez le niveau de garantie et orientez-vous vers une conception de réception idempotente.
Les deux derniers points sont des critères de repli assez importants.
FileSystemWatcher est pratique, mais ce n’est pas un détecteur de vérité universel.
7. Conclusion
FileSystemWatcher ne remplace pas une notification d’achèvement. La vérité ne réside pas dans la séquence d’événements, mais dans l’état actuellement visible sur le disque. Rendez l’achèvement explicite via temp -> close -> rename / replace ou via done / manifest, et décidez de la propriété en prenant un claim de façon atomique. C’est là que se trouve le cœur de la conception.
Traiter immédiatement sur Created, se fier au nombre ou à l’ordre des Changed, considérer l’arrêt des Changed comme un achèvement, se rassurer avec le seul InternalBufferSize, voir Error sans jamais s’en remettre - ce sont toutes des conceptions à éviter. À la place, repliez les notifications en demandes de réanalyse, prévoyez une réanalyse complète au démarrage / en cas d’overflow / lors d’une reconnexion, prenez la propriété par un claim rename, et absorbez les doublons et les réanalyses avec l’idempotence.
En somme, avec FileSystemWatcher, l’astuce consiste à ne jamais confondre « avoir reçu un événement » et « avoir le droit de traiter ».
Il suffit de séparer ces deux choses pour réduire nettement ce type de traitement de surveillance qui casse seulement de temps en temps.
8. Références
- Ensemble complet du code d’exemple de cet article (bibliothèque, démo, tests unitaires) https://github.com/gomurin0428/komurasoft-blog-samples/tree/main/filesystemwatcher-safe-basics
- Article associé : Les bases du contrôle d’exclusion pour l’intégration par fichiers - bonnes pratiques de verrouillage de fichiers et de claim atomique
- FileSystemWatcher Class (System.IO)
- System.IO.FileSystemWatcher class - .NET
- FileSystemWatcher.InternalBufferSize Property (System.IO)
- FileSystemWatcher.Error Event (System.IO)
- FileSystemWatcher.Created Event (System.IO)
- FileSystemWatcher.Changed Event (System.IO)
- FileSystemWatcher.Renamed Event (System.IO)
- Change Journals - Win32 apps
Articles associés
Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.
Pourquoi utiliser le Generic Host .NET et BackgroundService dans une application de bureau
Comment utiliser le Generic Host et BackgroundService pour organiser le démarrage, le traitement périodique, l'arrêt, la journalisation, ...
Versionner le schéma de base de données d'une application métier — pratiques de migration pour éviter que « chaque client ait une base différente »
Guide pratique pour versionner le schéma de base de données d'applications métier dont les bases sont dispersées chez chaque client. Impl...
CI/CD pratique pour les applications WinForms / WPF — Automatiser du build à la signature et à la distribution avec GitHub Actions
Guide pratique pour mettre en place le CI/CD des applications WinForms / WPF avec GitHub Actions. Couvre un YAML minimal de build+tests s...
Quand votre application Windows maison est signalée comme un virus — gérer les faux positifs de Microsoft Defender et composer avec l'impact sur les performances
Nous détaillons la marche à suivre officielle lorsque Microsoft Defender signale à tort une application Windows développée en interne com...
Veille, mise en veille prolongée, Modern Standby et applications longue durée — concevoir pour éviter « ça s'était arrêté pendant la nuit »
Pourquoi une application Windows censée tourner en continu se retrouve « arrêtée quand on la consulte le matin » : différences entre la v...
Sujets associés
Ces pages replacent le sujet dans un contexte plus large de services et de décisions.
Thèmes techniques Windows
Portail des sujets sur le développement Windows, l'analyse des incidents et la valorisation des actifs existants.
Services liés à ce sujet
Cet article est directement lié aux services suivants.
Développement d'applications Windows
Les outils d'intégration de fichiers et de surveillance construits autour de FileSystemWatcher sont un sujet qui revient fréquemment dans la pratique de notre développement d'applications Windows.
Conseil technique et revue de conception
Si vous souhaitez structurer les contre-mesures aux événements manqués, les réanalyses et la détection de l'achèvement en tant que conception, ce sujet se prête bien au conseil technique et à la revue de conception.
Questions fréquentes
Questions souvent posées lors d’une consultation sur le sujet de cet article.
- Peut-on lire le fichier dès l'événement Created de FileSystemWatcher ?
- Non. Created indique seulement que « le nom est devenu visible », il ne garantit pas qu'« il est déjà possible de lire ». Lors d'une copie ou d'un transfert, Created se déclenche au moment même où le fichier est créé, et un ou plusieurs Changed peuvent suivre ensuite. L'achèvement doit être rendu explicite côté émetteur via temp -> close -> rename/replace ou via un fichier done/manifest, et le récepteur doit se contenter de surveiller le nom final ou le fichier done.
- FileSystemWatcher peut-il manquer des notifications ?
- Oui. Quand le buffer interne (8192 octets par défaut, impossible de descendre sous 4096 octets, plafonné à 64 Ko) déborde, des notifications individuelles sont perdues et un événement Error se déclenche. Une fois qu'un overflow s'est produit, l'intégrité même de la séquence d'événements individuels devient douteuse ; il est donc plus sûr de faire une réanalyse complète (full rescan) du répertoire pour tout revérifier. Il faut aussi prévoir une réanalyse complète au démarrage, à la réception d'un Error, juste après avoir recréé le watcher, et périodiquement par précaution.
- Pourquoi l'événement Changed arrive-t-il plusieurs fois ?
- Parce que même une opération ordinaire comme un déplacement ou un enregistrement peut apparaître découpée en plusieurs événements, et qu'en plus, on capte aussi les accès faits par un antivirus ou un indexeur. Une conception qui se fie au nombre ou à l'ordre des événements est dangereuse. Il est plus stable de replier toutes les notifications en un seul type de signal — « demande de réanalyse » —, de concentrer l'analyse sur un seul worker, et de regrouper les rafales pendant environ 100 à 300 ms avant de lancer une seule analyse.
- Augmenter InternalBufferSize suffit-il à résoudre les pertes de notifications ?
- Non. Même en le portant jusqu'à 64 Ko, une rafale de notifications qui dépasse cette limite continuera à provoquer des pertes, et cela ne résout en rien la question de savoir si une notification signale réellement un achèvement. Le buffer utilisant de la non-paged memory, l'agrandir n'est pas non plus un geste anodin. L'ordre à suivre est plutôt : restreindre d'abord la surveillance avec Filter/NotifyFilter, revoir IncludeSubdirectories, alléger les gestionnaires d'événements, puis mettre en place la réanalyse complète (full rescan) et l'idempotence.
Profil de l’auteur
Page de présentation de l’auteur de l’article.
Go Komura
Représentant de KomuraSoft LLC
Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.
Liens publics