Avatar ·

Как в C# проверить, используется ли файл другим процессом?

Всем привет. Я новичок в C# и столкнулся с проблемой при работе с файлами. Мне нужно программно открывать текстовые файлы для чтения, но иногда они уже открыты в другой программе (например, в блокноте или Excel). В таких случаях мое приложение вылетает с исключением типа `IOException` и сообщением "The process cannot access the file because it is being used by another process". Вот пример моего кода: ```csharp string filePath = @"C:\Data\report.txt"; try { string content = File.ReadAllText(filePath); Console.WriteLine(content); } catch (IOException ex) { Console.WriteLine($"Ошибка: {ex.Message}"); } ``` Проблема в том, что я просто ловлю исключение, но хотелось бы заранее узнать, занят ли файл, и либо дождаться его освобождения, либо вывести пользователю понятное сообщение. Я пытался использовать `File.Exists()`, но это не помогает — файл существует, но заблокирован. Подскажите, есть ли в C# штатный способ проверить, используется ли файл в данный момент другим процессом? Или единственный вариант — это перехватывать исключение? Заранее спасибо! <<>>

Проверка блокировки файла через попытку открытия с монопольным доступом

Прямого метода, который бы возвращал true/false без риска изменить состояние файла, в .NET не существует. Однако есть надёжный и широко используемый подход — попытаться открыть файл с флагом FileShare.None (монопольный доступ). Если файл занят, вы получите исключение IOException, которое и укажет на блокировку.

Главное преимущество этого метода — он работает на всех версиях .NET и не требует внешних вызовов WinAPI. Вот реализация:

public static bool IsFileInUse(string filePath)
{
    try
    {
        using (FileStream fs = File.Open(filePath,
            FileMode.Open,
            FileAccess.ReadWrite,
            FileShare.None))
        {
            // Если дошли сюда — файл свободен
            return false;
        }
    }
    catch (IOException)
    {
        // Файл недоступен — скорее всего, заблокирован
        return true;
    }
    catch (UnauthorizedAccessException)
    {
        // Нет прав на чтение/запись — тоже считаем "занятым"
        return true;
    }
}

Как это работает:

  • FileMode.Open — открываем существующий файл.
  • FileAccess.ReadWrite — запрашиваем полный доступ.
  • FileShare.None — запрещаем доступ другим процессам на время проверки.

Если файл уже открыт другим процессом, вызов File.Open выбросит IOException, и мы вернём true. Если файл свободен — поток будет создан и сразу закрыт (благодаря using), вернётся false.

Важные нюансы

Этот метод не является «чистой» проверкой — он на мгновение блокирует файл. В многопоточной среде между проверкой и фактическим использованием файла его может занять другой процесс. Поэтому логичнее сразу пытаться открыть файл с нужными правами и обрабатывать исключение, а не проверять отдельно.

Пример правильного подхода с ожиданием освобождения:

public static string ReadFileWhenAvailable(string filePath, int maxRetries = 5, int delayMs = 500)
{
    for (int i = 0; i < maxRetries; i++)
    {
        try
        {
            using (FileStream fs = new FileStream(filePath,
                FileMode.Open,
                FileAccess.Read,
                FileShare.Read))
            using (StreamReader reader = new StreamReader(fs))
            {
                return reader.ReadToEnd();
            }
        }
        catch (IOException) when (i < maxRetries - 1)
        {
            Thread.Sleep(delayMs);
        }
    }
    throw new IOException($"Файл {filePath} не освободился после {maxRetries} попыток.");
}

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

Для продвинутых: использование WMI

Если нужно узнать, какой именно процесс заблокировал файл, придётся вызывать WinAPI через P/Invoke (функция NtQuerySystemInformation), либо использовать WMI-запросы. Но для 99% задач простой попытки открытия с FileShare.None более чем достаточно.

<<>>

Альтернативный подход: проверка через FileSystemWatcher и таймер

Если ваша задача — не просто проверить, а дождаться освобождения файла (например, для мониторинга логов), можно использовать комбинацию FileSystemWatcher и таймера. Однако это сложнее и не гарантирует моментального обнаружения.

public static void WaitForFileRelease(string filePath, Action onReleased)
{
    var watcher = new FileSystemWatcher(Path.GetDirectoryName(filePath))
    {
        Filter = Path.GetFileName(filePath),
        EnableRaisingEvents = true
    };

    Timer checkTimer = null;
    checkTimer = new Timer(_ =>
    {
        if (!IsFileInUse(filePath))  // используем метод из первого ответа
        {
            watcher.Dispose();
            checkTimer.Dispose();
            onReleased?.Invoke();
        }
    }, null, 0, 1000);
}

Недостатки этого подхода:

  • Ресурсоёмкость (постоянный опрос каждую секунду).
  • Задержка до 1 секунды между освобождением и реакцией.
  • Ненужная сложность для простых сценариев.

Поэтому рекомендую придерживаться первого варианта — прямой попытки открытия с повторными попытками. Это проще, надёжнее и эффективнее.