J'utilise la classe FileSystemWatcher
en C#
pour suivre les modifications sur un fichier chaque fois que l'utilisateur enregistre le fichier après l'avoir modifié.
watcher.EnableRaisingEvents = false; CreateFile(); watcher.EnableRaisingEvents = true;
Cependant, le fichier que je souhaite suivre est créé par programme, comme suit:
file.WriteFileToStream(stream);
Idéalement, mon code est censé fonctionner comme suit:
OnChanged
, c'est-à-dire que je veux que mon code de gestionnaire OnChanged
ne s'exécute que lorsqu'un utilisateur réel le modifie et l'enregistre.Cependant, il est déclenché chaque fois que le fichier est écrit par programme, c'est-à-dire sur la ligne suivante:
FileStream stream = FileUtil.CreateNewFile(filePath); // Creates a file file.WriteFileToStream(stream); // Writes into the file
Ce qui est, techniquement, un comportement correct, car il suit la modification du fichier. Cependant, mon analyse de rentabilité ne permet pas au code du gestionnaire OnChanged
d'être exécuté lorsque le fichier est initialement créé et écrit dans.
Ma question est la suivante: existe-t-il une solution de contournement pour ignorer l'appel OnChanged
lorsque le fichier est créé et écrit pour la première fois par programme?
Remarque:
FileSystemWatcher
soit initialisé au démarrage de mon application. Je ne peux donc pas l'enregistrer après la création du fichier.FileSystemWatcher watcher = new FileSystemWatcher() { Path = DIR_NAME, NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime | NotifyFilters.LastAccess, Filter = "*.pdf", IncludeSubdirectories = true, EnableRaisingEvents = true }; watcher.Changed += OnChanged;
4 Réponses :
C'est une situation délicate et vous ne pouvez pas faire grand-chose à part vérifier si le fichier est verrouillé (en supposant qu'il dispose d'un verrou en lecture ou en écriture):
public bool IsFileLocked(string fileName) { try { using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None)) { return false; } } catch (IOException) { //the file is unavailable return true; } }
La prémisse est quand vous obtenez une mise à jour, vous vérifiez si le fichier est verrouillé, si elle est pas alors vous pouvez supposer vaguement qu'il a été fermé.
Quelques points à considérer cependant:
FileAccess.Read
, donc cela n'échoue pas sur les fichiers en lecture seule.FileSystemWatcher
pour diverses raisons et il n'est pas toujours possible de s'y fier dans certaines situations; Lisez la documentation pour connaître les raisons pour lesquelles les événements peuvent être supprimés et comment les corriger. Dans ces cas, vous pourriez vous en tirer avec un long sondage et une certaine logique pour ramasser les retardataires (en fonction de votre situation).J'ai ajouté le code suivant dans le gestionnaire OnChanged
, et il semble fonctionner comme prévu.
private void OnChanged(object source, FileSystemEventArgs file) { if (file.ChangeType == WatcherChangeTypes.Created) return; FileInfo fileInfo = new FileInfo(file.FullPath); if (fileInfo.CreationTime == fileInfo.LastWriteTime) return; // Handle the Changed event ... }
Cependant, je ne suis pas sûr s'il me manque quelque chose qui provoquera une rupture. Des pensées?
Oui. Utilisez 2 gestionnaires différents pour Created
et Changed
. Je n'irai pas beaucoup plus loin dans les commentaires, mais vous avez ici des conditions de course que vous voulez éviter à tout prix (surtout dans les environnements multi-utilisateurs). N'oubliez pas non plus d'augmenter la taille du buffer FSW, cela vous évitera les mêmes maux de tête.
Si vous pouvez poster une réponse expliquant les conditions de course et la taille de la mémoire tampon, ce serait génial!
Un fichier peut être modifié alors que le handle est toujours ouvert, cela est susceptible d'échouer lorsque le flux de fichiers atteint le tampon, et vous obtiendrez un événement et le fichier sera toujours ouvert
Je vais y réfléchir. En bref: les événements FSW, quand beaucoup sont déclenchés, ont la mauvaise habitude de s'accumuler rapidement. Si vous ne les gérez pas correctement (avoir différents gestionnaires en fait partie), vous finirez par perdre / manquer des événements. L'augmentation de la taille de la mémoire tampon (par exemple, [FSW].InternalBufferSize = 32768;
) diminue le risque de mal gérer un événement. Une autre chose que vous voulez éviter est de coder tout ce qui gère les conséquences de l'événement sur place (à l'intérieur du gestionnaire). Je mets généralement en file d'attente l'événement empaqueté à l' aide d'une méthode proxy qui limite les données à des tâches de threadpool pour le traitement.
Approche un:
Approche deux:
Lorsqu'un fichier est créé, utilisez le gestionnaire d'événements OnCreated()
pour ajouter le nom de fichier à une liste filesToIgnore
.
Dans le gestionnaire d'événements OnChanged
, vérifiez si la liste filesToIgnore
contient le nom du fichier. Si c'est le cas, supprimez-le de la liste (pour le traiter la prochaine fois) et retournez-le depuis le gestionnaire.
private List<string> filesToIgnore = new List<string>(); private void OnCreated(object source, FileSystemEventArgs file) { filesToIgnore.Add(file.Name); } private void OnChanged(object source, FileSystemEventArgs file) { if(filesToIgnore.Contains(file.Name)) { filesToIgnore.Remove(file.Name); return; } // Code to execute when user saves the file }
Cette approche suppose que OnCreated()
sera toujours déclenché avant OnChanged().
Vous devrez peut-être affiner le NotifyFilter
votre FSW. En fait, vous [FSW].NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
avez besoin que de deux: [FSW].NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
. Avec ceux-ci, vous serez averti lorsqu'un fichier est créé, supprimé, renommé et modifié. Notez que l'événement Changed
est, la plupart du temps (sinon toujours) déclenché deux fois. Lorsqu'un fichier est créé pour la première fois, seul l'événement Created est déclenché.
La méthode d'extension ci-dessous permet de gérer les événements des actions de l'utilisateur uniquement:
var fsw = new FileSystemWatcher(DIR_NAME); fsw.OnChangedByUser(File_ChangedByUser); fsw.EnableRaisingEvents = true; private static void File_ChangedByUser(object sender, FileSystemEventArgs e) { // Handle the event }
Exemple d'utilisation:
public static void OnChangedByUser(this FileSystemWatcher fsw, FileSystemEventHandler handler) { const int TOLERANCE_MSEC = 100; object locker = new object(); string fileName = null; Stopwatch stopwatch = new Stopwatch(); fsw.Created += OnFileCreated; fsw.Changed += OnFileChanged; fsw.Disposed += OnDisposed; void OnFileCreated(object sender, FileSystemEventArgs e) { lock (locker) { fileName = e.Name; stopwatch.Restart(); } } void OnFileChanged(object sender, FileSystemEventArgs e) { lock (locker) { if (e.Name == fileName && stopwatch.ElapsedMilliseconds < TOLERANCE_MSEC) { return; // Ignore this event } } handler.Invoke(sender, e); } void OnDisposed(object sender, EventArgs e) { fsw.Created -= OnFileCreated; fsw.Changed -= OnFileChanged; fsw.Disposed -= OnDisposed; } }
Pourquoi utilisez-vous le même gestionnaire (
OnChanged
) pourCreated
etChanged
? L'utilisateur, apparemment, ne modifie que le fichier. Il en va de même pourDeleted
.J'essayais différentes options disponibles et j'ai oublié de les supprimer. Cependant, le problème d'origine demeure. Je veux ignorer le premier déclencheur.
Eh bien, affectez différents gestionnaires d'événements et voyez quelle est la séquence d'événements qui se déclenchent. Je suis presque sûr que vous comprendrez qu'il existe un chemin que vous pouvez suivre pour déterminer quand un fichier est en cours de création ou de modification. Étant donné que votre application est la seule partie à créer ces fichiers ...
J'ai attaché différents gestionnaires tels que
OnCreated
etOnDeleted
. Cependant, ils ne sont pas déclenchés lorsque je supprime ou crée un fichier. Je ne sais pas ce que je manque.Vous avez fait quelque chose de mal :) Lorsque vous créez un fichier et que votre gestionnaire
Created
est correctement défini, c'est le seul événement qui est déclenché. Ie,Changed
n'est pas soulevé.Je vois maintenant que vos
NotifyFilters
n'incluent pasNotifyFilters.FileName
. Ajoutez cela aussi.Oui, cela l'a corrigé. Supprimés / créés fonctionnent maintenant. Merci!