1
votes

FileSystemWatcher: ignorer la première modification du fichier

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:

  1. Le programme créera un fichier et y inscrira du contenu.
  2. L'utilisateur ouvre le fichier, le modifie et l'enregistre.
  3. À ce stade, il devrait déclencher 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:

  1. L'architecture de l'application nécessite que FileSystemWatcher soit initialisé au démarrage de mon application. Je ne peux donc pas l'enregistrer après la création du fichier.
  2. Il s'agit d'une application multi-utilisateurs, dans laquelle plusieurs utilisateurs écriront simultanément dans les fichiers, je ne peux donc pas désactiver l'observateur avant de créer le fichier et l'activer après sa création:

FileSystemWatcher watcher = new FileSystemWatcher()
{
    Path = DIR_NAME,
    NotifyFilter = NotifyFilters.LastWrite | 
                   NotifyFilters.CreationTime | 
                   NotifyFilters.LastAccess,
    Filter = "*.pdf",
    IncludeSubdirectories = true,
    EnableRaisingEvents = true
};

watcher.Changed += OnChanged;


7 commentaires

Pourquoi utilisez-vous le même gestionnaire ( OnChanged ) pour Created et Changed ? L'utilisateur, apparemment, ne modifie que le fichier. Il en va de même pour Deleted .


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 et OnDeleted . 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 pas NotifyFilters.FileName . Ajoutez cela aussi.


Oui, cela l'a corrigé. Supprimés / créés fonctionnent maintenant. Merci!


4 Réponses :


0
votes

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:

  1. Il peut y avoir une condition de concurrence lorsque vous vérifiez que le fichier est verrouillé, que tout est effacé, puis que vous découvrez qu'un autre processus a verrouillé le fichier.
  2. J'ai utilisé FileAccess.Read , donc cela n'échoue pas sur les fichiers en lecture seule.
  3. Les événements peuvent être supprimés de 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).


0 commentaires

0
votes

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?


4 commentaires

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.



1
votes

Approche un:

  1. Créez et enregistrez le fichier dans un répertoire qui n'est pas surveillé.
  2. Déplacez le fichier dans le répertoire surveillé.

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().


1 commentaires

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



0
votes

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;
    }
}


0 commentaires