Многопоточная обработка файлов при использовании FileWatcher.

  • Михаил
  • 8 мин. на прочтение
  • 79
  • 06 Aug 2024
  • 06 Aug 2024

В прошлых статьях рассматривали вопрос использования FileWatcher для “правильного” использования файлов для обмена информацией. Можно привести бесконечное множество примеров, когда используются файлы для хранения конечного или промежуточного результата. Но вот возникла проблема, когда срабатывает watcher.Changedно при этом файл еще занят, и приложение вываливается с ошибкой. Исправим это недоразумение, да и сделаем обработку файлов многопоточной, чтобы избежать блокировки при обработке одного файла.

private bool isFileBeingProcessed = false;
private int maxRetryCount = 5;
private int retryDelay = 1000; // 1 секунда
private SemaphoreSlim semaphore = new SemaphoreSlim(1, 1); // Ограничиваем до 1 потока
private void FileWatcher_Changed(object sender, FileSystemEventArgs e)
{
   if (!isFileBeingProcessed)
   {
       isFileBeingProcessed = true;
       // Запускаем обработку файла в отдельном потоке
       Task.Run(() => ProcessFileAsync(e.FullPath));
       isFileBeingProcessed = false;
   }
}
private async Task ProcessFileAsync(string filePath)
{
   try
   {
       // Ожидаем, пока не освободится семафор
       await semaphore.WaitAsync();
       int retryCount = 0;
       bool success = false;
       // Пытаемся обработать файл до тех пор, пока он не будет освобожден
       while (!success && retryCount < maxRetryCount)
       {
           try
           {
               // Обрабатываем файл
               await ProcessFileInternalAsync(filePath);
               success = true;
           }
           catch (IOException ex)
           {
               // Если файл все еще занят, ждем и пытаемся снова
               Console.WriteLine($"Файл {filePath} занят. Попытка {retryCount + 1} из {maxRetryCount}. Ошибка: {ex.Message}");
               retryCount++;
               await Task.Delay(retryDelay);
           }
       }
       if (!success)
       {
           Console.WriteLine($"Не удалось обработать файл {filePath} после {maxRetryCount} попыток.");
       }
   }
   finally
   {
       // Освобождаем семафор
       semaphore.Release();
   }
}
private async Task ProcessFileInternalAsync(string filePath)
{
   // Обрабатываем файл
   Console.WriteLine($"Обрабатываем файл: {filePath}");
   await Task.Delay(2000); // Имитируем длительную обработку
}

Основные изменения:

1. Добавили SemaphoreSlim, чтобы ограничить количество одновременно обрабатываемых файлов до 1.
2. Вынесли обработку файла в отдельный метод ProcessFileAsync, который запускается в отдельном потоке с помощью Task.Run.
3. В ProcessFileAsync мы ожидаем, пока не освободится семафор, чтобы гарантировать, что только один поток обрабатывает файл.
4. Внутри ProcessFileAsync мы вызываем ProcessFileInternalAsync, где находится основная логика обработки файла.
5. В ProcessFileInternalAsync мы имитируем длительную обработку файла с помощью Task.Delay(2000).
6. После завершения обработки мы освобождаем семафор в блоке finally.

Теперь, даже если FileWatcher_Changed сработает на другой файл, он не будет блокировать обработку текущего файла, так как обработка происходит в отдельном потоке, контролируемом семафором.