Ошибка доступа к файлу с FileSystemWatcher, когда несколько файлов добавляются в каталог - PullRequest
37 голосов
/ 31 марта 2009

Я столкнулся с проблемой с FileSystemWatcher, когда несколько файлов помещаются в наблюдаемый каталог. Я хочу разобрать файл, как только он будет помещен в каталог. Как правило, первый файл анализируется нормально, но добавление второго файла в каталог вызывает проблемы с доступом. Иногда первый файл даже не анализируется. Существует только одно приложение, работающее и просматривающее этот каталог. В конце концов, этот процесс будет запущен на нескольких машинах, и они будут наблюдать за общим каталогом, но только один сервер может анализировать каждый файл, поскольку данные импортируются в базу данных, а первичные ключи отсутствуют.

Вот код 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);
}

Затем метод, который анализирует файл:

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

При перемещении второго файла он ловит

System.IO.IOException: процесс не может получить доступ к файлу 'C: \ Temp \ TestFile.txt', поскольку он используется другим процессом.

Я бы ожидал увидеть эту ошибку, если бы она работала на нескольких машинах, но пока она работает только на одном сервере. Не должно быть другого процесса, использующего этот файл - я создал его и скопировал в каталог, когда приложение запущено.

Это правильный способ настройки FileSystemWatcher? Как я могу увидеть, что блокирует этот файл? Почему он не анализирует оба файла - я должен закрыть FileStream? Я хочу сохранить опцию FileShare.None, потому что я хочу, чтобы только один сервер анализировал файл - сервер, который получает файл, сначала анализирует его.

Ответы [ 9 ]

52 голосов
/ 31 марта 2009

Типичная проблема этого подхода заключается в том, что файл все еще копируется во время запуска события. Очевидно, вы получите исключение, потому что файл заблокирован во время копирования. Исключение особенно вероятно для больших файлов.

В качестве обходного пути вы можете сначала скопировать файл, а затем переименовать его и прослушать событие переименования.

Или другой вариант - иметь цикл while, проверяющий, можно ли открыть файл с правами записи. Если это возможно, вы будете знать, что копирование было завершено. Код C # может выглядеть следующим образом (в рабочей системе вам может потребоваться максимальное количество попыток или время ожидания вместо 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);
    }
}

Еще один подход заключается в размещении небольшого триггерного файла в папке после завершения копирования. Ваш FileSystemWatcher будет прослушивать только файл триггера.

9 голосов
/ 22 сентября 2011

Я бы оставил комментарий выше, но у меня пока недостаточно очков.

Лучший ответ на этот вопрос имеет блок кода, который выглядит следующим образом:

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

Проблема с использованием параметра FileShare.ReadWrite заключается в том, что он запрашивает доступ к файлу, в основном говоря: «Я хочу читать / писать в этот файл, но другие также могут читать / писать в него». Этот подход потерпел неудачу в нашей ситуации. Процесс, который получал удаленную передачу, не устанавливал блокировку файла, но активно записывал в него. Наш нисходящий код (SharpZipLib) не выполнялся с исключением «файл используется», потому что он пытался открыть файл с FileShare.Read («Я хочу, чтобы файл читался, и только другие процессы также могли читать»). Поскольку процесс, у которого был открыт файл, уже записывал в него, этот запрос не удался.

Однако код в ответе выше слишком расслаблен. Используя FileShare.ReadWrite, ему удалось получить доступ к файлу (потому что он запрашивал ограничение общего ресурса, которое могло бы быть выполнено), но нисходящий вызов продолжал давать сбой.

Настройка общего доступа при вызове File.Open должна быть FileShare.Read или FileShare.None, а НЕ FileShare.ReadWrite.

4 голосов
/ 16 мая 2009

Когда вы открываете файл в методе OnChanged, вы указываете FileShare.None, что в соответствии с документацией приведет к тому, что любые другие попытки открыть файл будут неудачными, пока вы его получили открыть. Поскольку все, что вы (и ваш наблюдатель) делаете, это чтение, попробуйте вместо этого использовать FileShare.Read.

2 голосов
/ 01 февраля 2012

FileSystemWatcher запускает watcher.Created событие два раза для каждого отдельного создания файла 1ce, когда копирование файла начато, и 2 раза, когда копирование файла закончено. Все, что вам нужно сделать, это проигнорировать 1-е событие и обработать событие во второй раз.

Простой пример обработчика событий:

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 голосов
/ 11 февраля 2011

Простым решением было бы избавиться от файловой системы, когда вы получите уведомление. перед копированием файла заставьте текущий поток ждать, пока он не получит событие удаления файловой системы. Затем вы можете продолжить копирование измененного файла без проблем с доступом. У меня было такое же требование, и я сделал это точно так же, как я упомянул. это сработало.

Пример кода:

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);
}
1 голос
/ 08 августа 2016

У меня была похожая проблема. Это только из-за FileSystemWatcher. Я просто использовал
Thread.Sleep ();

И теперь работает нормально. Когда файл попадает в каталог, он вызывает onCreated дважды. так один раз, когда файл копируется. и второй раз, когда копирование завершено. Для этого я использовал Thread.Sleep (); Так что будет ждать, прежде чем я вызову 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 голос
/ 14 июня 2011

Мне кажется, хорошим примером того, что вы хотите, является ConfigureAndWatchHandler в log4net. Они используют таймер для запуска события обработчика файла. Я чувствую, что это в итоге является более чистой реализацией цикла while в посте 0xA3. Для тех из вас, кто не хочет использовать dotPeek для проверки файла, я постараюсь дать здесь фрагмент кода на основе кода 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);
    }
}
0 голосов
/ 04 июля 2012
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 голосов
/ 16 мая 2009

У меня была такая же проблема в DFS. Мое разрешение было достигнуто путем добавления двух пустых строк в каждый файл. Тогда мой код ждет две пустые строки в файле. Тогда у меня есть уверенность, чтобы прочитать все данные из файла.

...