Erreur d'accès aux fichiers avec FileSystemWatcher lorsque plusieurs fichiers sont ajoutés à un répertoire

j'ai un problème avec un gestionnaire de fichiers lorsque plusieurs fichiers sont placés dans le répertoire regardé. Je veux analyser le fichier dès qu'il est placé dans le répertoire. Typiquement, le premier fichier est parfait, mais l'ajout d'un second fichier au répertoire provoque un problème d'accès. Parfois, le premier fichier ne passe même pas. Il n'y a qu'une application qui tourne et regarde ce répertoire. Finalement, ce processus sera exécuté sur plusieurs machines, et ils seront regarder un répertoire partagé, mais un seul serveur peut analyser chaque fichier, les données sont importées dans une base de données et il n'y a pas de clés primaires.

<!-Voici le code FileSystemWatcher:

public void Run() {
  FileSystemWatcher watcher = new FileSystemWatcher("C:temp");
  watcher.NotifyFilter = NotifyFilters.FileName;
  watcher.Filter = "*.txt";

  watcher.Created += new FileSystemEventHandler(OnChanged);

  watcher.EnableRaisingEvents = true;
  System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
}

Ensuite, la méthode qui analyse le fichier:

private void OnChanged(object source, FileSystemEventArgs e) {
  string line = null;

  try {
    using (FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.None)) {
      using (StreamReader sr = new StreamReader(fs)) {
        while (sr.EndOfStream == false) {
          line = sr.ReadLine();
          //parse the line and insert into the database
        }
      }
    }
  }
  catch (IOException ioe) {
    Console.WriteLine("OnChanged: Caught Exception reading file [{0}]", ioe.ToString());
  }

en déplaçant le second fichier, il attrape

Système.IO.IOException: le processus ne peut pas accéder au fichier "C:TempTestFile.txt", car il est utilisé par un autre processus.

Je m'attendrais à voir cette erreur si elle tournait sur plusieurs machines, mais elle ne tourne que sur un seul serveur pour le moment. Il ne devrait pas y avoir d'autre processus utilisant ce fichier - je les ai créés et copiés dans le répertoire lorsque l'application est en cours d'exécution.

est - ce que c'est la bonne façon de configurer FileSystemWatcher? Comment puis-je voir quel est le verrou sur ce fichier? Pourquoi ne pas analyser les fichiers dois-je fermer la FileStream? Je veux gardez le partage de fichiers.Aucune option parce que je ne veux qu'un serveur pour analyser le fichier - le serveur qui arrive au fichier le analyse en premier.

37
demandé sur abatishchev 2009-03-31 04:09:16

9 réponses

Un problème typique de cette approche est que le fichier est toujours copiés lors de l'événement est déclenché. Évidemment, vous obtiendrez une exception parce que le fichier est verrouillé pendant la copie. Une exception est particulièrement probable pour les gros dossiers.

pour contourner le problème, vous pouvez d'abord copier le fichier, puis le renommer et écouter l'événement renommé.

Ou une autre option serait d'avoir une boucle while vérifier si le fichier peut être ouvert avec accès en écriture. Si cela peut vous saura que la copie est terminée. C # code pourrait ressembler à ceci (dans un système de production vous pourriez vouloir avoir un nombre maximum de tentatives ou de timeout au lieu d'un while(true)):

/// <summary>
/// Waits until a file can be opened with write permission
/// </summary>
public static void WaitReady(string fileName)
{
    while (true)
    {
        try
        {
            using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
            {
                if (stream != null)
                {
                    System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
                    break;
                }
            }
        }
        catch (FileNotFoundException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        catch (IOException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        catch (UnauthorizedAccessException ex)
        {
            System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} not yet ready ({1})", fileName, ex.Message));
        }
        Thread.Sleep(500);
    }
}

une autre approche serait de placer un petit fichier de déclenchement dans le dossier une fois la copie terminée. Votre gestionnaire de fichiers n'écouterait que le fichier de déclenchement.

51
répondu Dirk Vollmar 2009-03-31 08:58:32

j'aurais laissé un commentaire ci-dessus, mais je n'ai pas assez de points encore.

la réponse la mieux cotée à cette question a un bloc de code qui ressemble à ceci:

using (Stream stream = System.IO.File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
    if (stream != null)
    {
        System.Diagnostics.Trace.WriteLine(string.Format("Output file {0} ready.", fileName));
        break;
    }
}

le problème avec l'utilisation de FileShare.ReadWrite paramètre est que c'est la demande d'accès au fichier qui revient à dire "je veux lire/écrire dans ce fichier, mais d'autres peuvent également lire/écrire."Cette approche a échoué dans notre situation. Le processus qui recevait le transfert à distance n'a pas mis un verrou sur le fichier, mais il lui écrivait activement. Notre code en aval (SharpZipLib) échouait avec l'exception "file in use" parce qu'il essayait d'ouvrir le fichier avec un FileShare.Read ("je veux que le fichier soit lu, et que les autres processus le soient aussi"). Parce que le processus qui avait le dossier ouvert était déjà écrit à lui, cette demande a échoué.

cependant, le code dans la réponse ci-dessus est trop détendu. À l'aide de FileShare.ReadWrite, il a réussi à obtenir l'accès au dossier (parce qu'il a été demandant une restriction de parts qui pourrait être respectée), mais l'appel en aval a continué d'échouer.

le paramètre de partage dans l'appel à File.Open doit être FileShare.Read ou FileShare.None et FileShare.ReadWrite.

9
répondu G-Mac 2014-07-15 16:19:07

lorsque vous ouvrez le fichier dans votre méthode OnChanged, vous spécifiez FileShare.None, qui, selon la documentation, fera échouer toute autre tentative d'ouvrir le fichier tant que vous l'aurez ouvert. Puisque tout ce que vous (et votre observateur) faites est de lire, essayez d'utiliser FileShare.Read à la place.

4
répondu Mike Powell 2009-05-16 13:01:01

la solution Simple serait d'éliminer le filesystemwatcher une fois que vous recevez la notification. avant de copier le fichier, faites attendre le thread courant jusqu'à ce qu'il reçoive l'événement FileSystemWatcher dispose. ensuite, vous pouvez continuer à copier le fichier modifié sans les problèmes d'accès. J'ai eu la même exigence et j'ai fait exactement comme ce que j'ai mentionné. il a travaillé.

Exemple De Code:

public void TestWatcher()
{
    using (var fileWatcher = new FileSystemWatcher())
    {

        string path = @"C:\sv";
        string file = "pos.csv";

        fileWatcher.Path = path;
        fileWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
        fileWatcher.Filter = file;

        System.EventHandler onDisposed = (sender,args) =>
        {
           eve.Set();
        };

        FileSystemEventHandler onFile = (sender, fileChange) =>
        {
           fileWatcher.EnableRaisingEvents = false;
           Thread t = new Thread(new ParameterizedThreadStart(CopyFile));
           t.Start(fileChange.FullPath);
           if (fileWatcher != null)
           {
               fileWatcher.Dispose();
           }
           proceed = false;
        };

        fileWatcher.Changed += onFile;
        fileWatcher.Created += onFile;
        fileWatcher.Disposed+= onDisposed;
        fileWatcher.EnableRaisingEvents = true;

        while (proceed)
        {
            if (!proceed)
            {
                break;
            }
        }
    }
}

public void CopyFile(object sourcePath)
{
    eve.WaitOne();
    var destinationFilePath = @"C:\sv\Co";
    if (!string.IsNullOrEmpty(destinationFilePath))
    {
        if (!Directory.Exists(destinationFilePath))
        {
            Directory.CreateDirectory(destinationFilePath);
        }
        destinationFilePath = Path.Combine(destinationFilePath, "pos.csv");
    }           

    File.Copy((string)sourcePath, destinationFilePath);
}
2
répondu Aravind Kathiroju 2015-06-24 13:18:39

FileSystemWatcher guetteur de feu.Événement créé deux fois pour chaque création de fichier 1ce lorsque la copie de fichier est commencée et la 2e fois lorsque la copie de fichier est terminée. Tout ce que vous avez à faire est d'ignorer le 1er événement et de traiter l'événement la deuxième fois.

Un exemple simple de gestionnaire d'événement:

private bool _fileCreated = false;
private void FileSystemWatcher_FileCreated(object sender, FileSystemEventArgs e)
{
    if (_fileCreated)
    {
        ReadFromFile();//just an example method call to access the new file
    }

    _fileCreated = !_fileCreated;
}
2
répondu AjitChahal 2015-06-24 13:19:16

je pense qu'un bon exemple de ce que vous voulez est la configuration Etwatchhandler dans log4net. Ils utilisent une minuterie pour lancer l'Événement du gestionnaire de fichiers. Je pense que cela finit par être une implémentation plus propre de la boucle while dans le post de 0xA3. Pour ceux d'entre vous qui ne veulent pas utiliser dotPeek pour examiner le fichier, je vais essayer de vous donner un extrait de code basé sur le code OP:

private System.Threading.Timer _timer;    

public void Run() {
  //setup filewatcher
  _timer = new System.Threading.Timer(new TimerCallback(OnFileChange), (object) null, -1, -1);
}

private void OnFileChange(object state)
{
    try 
    {
    //handle files
    }
    catch (Exception ex) 
    {
        //log exception
        _timer.Change(500, -1);
    }
}
1
répondu Druegor 2015-06-24 14:23:53

j'ai eu le même problème. C'est juste à cause de FileSystemWatcher. J'ai juste utilisé

Fil.Dormir();

Et sa fonctionne bien maintenant. Quand le dossier vient dans l'annuaire il appelle onCreated deux fois. donc une fois quand le fichier est copié.et la deuxième fois quand la copie est terminée. Pour cela, j'ai utilisé du Fil.Sleep(); Donc, il faudra attendre avant que j'appelle ReadFile();

private static void OnCreated(object source, FileSystemEventArgs e)
    {
        try
        {
            Thread.Sleep(5000);
            var data = new FileData();
            data.ReadFile(e.FullPath);                
        }
        catch (Exception ex)
        {
            WriteLogforError(ex.Message, String.Empty, filepath);
        }
    }
1
répondu Nikhil Patel 2016-08-08 09:19:34

j'ai eu le même problème au sein de la DSV. Ma résolution a été réalisée en ajoutant deux lignes vides à chaque fichier. Alors mon code attend deux lignes vides dans le fichier. Alors j'ai la certitude de lire des données entières à partir du fichier.

0
répondu dariol 2009-05-16 12:46:00
public static BitmapSource LoadImageNoLock(string path)
{
    while (true)
    {
        try
        {
            var memStream = new MemoryStream(File.ReadAllBytes(path));
            var img = new BitmapImage();
            img.BeginInit();
            img.StreamSource = memStream;
            img.EndInit();
            return img;
            break;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}
0
répondu Andreas 2015-06-24 13:20:32