Подтвердить что ты не робот

Ошибка доступа к файлу с помощью FileSystemWatcher, когда в каталог добавлено несколько файлов

У меня возникает проблема с 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, потому что я хочу, чтобы только один сервер разбирал файл - сервер, который попадает в файл, сначала анализирует его.

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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, ему удалось получить доступ к файлу (потому что он запрашивал ограничение на Share, которое может быть выполнено), но вызов по нисходящему каналу продолжал сбой.

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

Ответ 3

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

Ответ 4

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

Пример кода:

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

Ответ 5

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

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

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

Ответ 6

Я считаю хорошим примером того, что вы хотите - 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);
    }
}

Ответ 7

У меня была аналогичная проблема. Это просто из-за 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);
        }
    }

Ответ 8

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

Ответ 9

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