Błąd dostępu do plików przy użyciu FileSystemWatcher, gdy do katalogu dodawanych jest wiele plików

Mam problem z FileSystemWatcher, gdy wiele plików jest umieszczanych w obserwowanym katalogu. Chcę przetworzyć plik zaraz po umieszczeniu go w katalogu. Zazwyczaj pierwszy plik przetwarza się poprawnie, ale dodanie drugiego pliku do katalogu powoduje problem z dostępem. Czasami pierwszy plik nawet nie analizuje. Jest tylko jedna aplikacja uruchomiona i obserwująca ten katalog. Ostatecznie proces ten będzie działał na wielu maszynach i będą one obserwowanie współdzielonego katalogu, ale tylko jeden serwer może przetwarzać każdy plik, gdy dane są importowane do bazy danych i nie ma kluczy głównych.

Oto Kod 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);
}

Następnie metoda parsująca plik:

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

Podczas przesuwania drugiego pliku, to wyłapuje

System. IO. IOException: proces nie może uzyskać dostępu do pliku 'C:\Temp\TestFile.txt ' ponieważ jest on używany przez inny proces.

Spodziewałbym się tego błąd, jeśli działał na wielu komputerach, ale na razie działa tylko na jednym serwerze. Nie powinno być innego procesu używającego tego pliku - MAM JE utworzone i skopiowane do katalogu, gdy aplikacja jest uruchomiona.

Czy to jest właściwy sposób na skonfigurowanie FileSystemWatcher? Jak Mogę zobaczyć, co ma blokadę w tym pliku? Dlaczego nie analizuje obu plików-Czy muszę zamykać strumień plików? Chcę zachować pliki.Brak opcji, ponieważ chcę tylko jeden serwer do parsowania plik-serwer, który dostaje się do pliku jako pierwszy go przetwarza.

Author: abatishchev, 2009-03-31

9 answers

Typowym problemem tego podejścia jest to, że plik jest nadal kopiowany podczas wywołania zdarzenia. Oczywiście otrzymasz wyjątek, ponieważ plik jest zablokowany podczas kopiowania. Wyjątek jest szczególnie prawdopodobny w przypadku dużych plików.

Jako obejście można najpierw skopiować plik, a następnie zmienić jego nazwę i odsłuchać zdarzenie zmiany nazwy.

Lub inną opcją byłoby posiadanie pętli while sprawdzającej, czy plik można otworzyć z dostępem do zapisu. Jeśli to możliwe, będziesz wiedział, że kopiowanie zostało zakończone. Kod C# może wyglądać tak (w systemie produkcyjnym możesz chcieć mieć maksymalną liczbę powtórzeń lub timeout zamiast 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);
    }
}

Kolejnym podejściem byłoby umieszczenie małego pliku wyzwalającego w folderze po zakończeniu kopiowania. Twój FileSystemWatcher będzie nasłuchiwał tylko Pliku wyzwalającego.

 51
Author: Dirk Vollmar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-31 08:58:32

Zostawiłbym komentarz powyżej, ale nie mam jeszcze wystarczająco punktów.

Najlepiej oceniana odpowiedź na to pytanie ma blok kodu, który wygląda tak:

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

Problem z używaniem ustawienia FileShare.ReadWrite polega na tym, że żąda dostępu do pliku w zasadzie mówiąc "chcę czytać/zapisywać do tego pliku, ale inni mogą również odczytywać/zapisywać do niego.- Takie podejście zawiodło w naszej sytuacji. Proces, który odbierał zdalny transfer, nie zablokował pliku, ale był aktywnie piszę do niego. Nasz dalszy kod (SharpZipLib) nie powiódł się z wyjątkiem "file in use", ponieważ próbował otworzyć plik za pomocą FileShare.Read ("chcę plik do odczytu i pozwól innym procesom również czytać"). Ponieważ proces, który miał otwarty plik, już do niego zapisywał, to żądanie nie powiodło się.

Jednak kod w powyższej odpowiedzi jest zbyt luźny. Używając FileShare.ReadWrite, udało mu się uzyskać dostęp do pliku (ponieważ prosiło o ograniczenie udziału które można było uhonorować), ale dalsza rozmowa nadal nie powiodła się.

Ustawienie udziału w wywołaniu do File.Open powinno być FileShare.Read lub FileShare.None, a Nie FileShare.ReadWrite.

 9
Author: G-Mac,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-15 16:19:07

Kiedy otwierasz plik w metodzie OnChanged, określasz FileShare.None, co zgodnie z dokumentacją spowoduje, że wszelkie inne próby otwarcia pliku nie powiodą się, gdy go otworzysz. Ponieważ ty (i twój obserwator) tylko czytasz, spróbuj użyć FileShare.Read.

 4
Author: Mike Powell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-05-16 13:01:01

Prostym rozwiązaniem byłoby pozbycie się filesystemwatcher po otrzymaniu powiadomienia. przed skopiowaniem pliku spraw, aby bieżący wątek poczekał, aż otrzyma usunięte Zdarzenie filesystemwatcher. następnie możesz kontynuować kopiowanie zmienionego pliku bez problemów z dostępem. Miałem ten sam wymóg i zrobiłem to dokładnie tak, jak wspomniałem. zadziałało.

Przykładowy Kod:

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
Author: Aravind Kathiroju,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 13:18:39

FileSystemWatcher uruchamia watchera.Utworzone Zdarzenie dwa razy dla każdego utworzenia pliku 1ce po rozpoczęciu kopiowania pliku i 2. po zakończeniu kopiowania pliku. Wszystko, co musisz zrobić, to zignorować Zdarzenie 1st i przetworzyć zdarzenie po raz drugi.

Prosty przykład obsługi zdarzeń:

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
Author: AjitChahal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 13:19:16

Uważam, że dobrym przykładem tego, czego chcesz, jest ConfigureAndWatchHandler w log4net. Używają timera do odpalenia zdarzenia obsługi plików. Wydaje mi się, że kończy się to czystszą implementacją pętli while w poście 0xa3. Dla tych z was, którzy nie chcą używać dotPeek do sprawdzania pliku postaram się dać wam fragment kodu oparty na kodzie 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
Author: Druegor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 14:23:53

Miałem podobny problem. To tylko z powodu FileSystemWatcher. I just used
Nić.Sleep ();

I teraz działa dobrze. Gdy plik wchodzi do katalogu, wywołuje on onCreated dwukrotnie. więc raz, gdy plik jest kopiowany.i drugi raz po zakończeniu kopiowania. Do tego użyłem wątku.Sleep (); więc będzie czekać zanim wywołam 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
Author: Nikhil Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-08 09:19:34

Miałem ten sam problem w DFS. Moja rozdzielczość została osiągnięta przez dodanie dwóch pustych linii do każdego pliku. Następnie Mój kod czeka na dwie puste linie w pliku. Wtedy mam pewność, aby odczytać całe dane z pliku.

 0
Author: dariol,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
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
Author: Andreas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-24 13:20:32