Как в 1С проверить доступность файла и избежать ошибки совместного доступа?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки механизмов обмена данными мы часто сталкиваемся с ситуацией, когда один и тот же ресурс (файл) пытаются использовать одновременно несколько процессов. Классический пример: регламентное задание выполняет выгрузку по расписанию каждые 30 минут, и в это же время пользователь решает запустить аналогичную выгрузку вручную через обработку. В результате возникает системная ошибка совместного доступа, которая может прервать выполнение кода и вызвать неудобства у пользователя.

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

Причина возникновения проблемы и ограничение оператора «Попытка»

Многие начинающие разработчики полагают, что достаточно обернуть метод открытия или записи файла в блок Попытка. Однако, как показывает практика, это срабатывает не всегда. Выясним причину: некоторые внешние компоненты или системные вызовы при попытке доступа к заблокированному файлу вызывают диалоговое окно операционной системы или внутреннюю ошибку компоненты, которую интерпретатор 1С не может корректно перехватить. Кроме того, файл может быть заблокирован операционной системой на уровне файловой системы (File Lock), и обращение к нему приведет к «вылету» сеанса или длительному зависанию.

Решение 1: Использование файлов-маркеров (флаг-файлов)

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

Разберем алгоритм работы этого метода:

  1. Перед началом выгрузки (и в фоновом задании, и в обработке) мы проверяем наличие файла с именем lock.txt в рабочем каталоге.
  2. Если файл существует, мы понимаем, что процесс занят, и просто выводим уведомление пользователю, не начиная основную работу.
  3. Если файла нет, мы создаем его, выполняем выгрузку, а по завершении — удаляем его.

Посмотрим на пример реализации такого механизма:


ИмяФайлаБлокировки = ПутьККаталогу + "export_lock.tmp";
ФайлБлокировки = Новый Файл(ИмяФайлаБлокировки);

Если ФайлБлокировки.Существует() Тогда
    Состояние("Файл занят другим процессом. Попробуйте позже.");
    Возврат;
КонецЕсли;

// Создаем пустой файл-маркер
ТекстБлокировки = Новый ЗаписьТекста(ИмяФайлаБлокировки);
ТекстБлокировки.Закрыть();

Попытка
    // Здесь выполняется основной код выгрузки данных
    ВыполнитьВыгрузкуВФайл(ЦелевойФайл);
Исключение
    Сообщить("Произошла ошибка при выгрузке: " + ОписаниеОшибки());
КонецПопытки;

// Обязательно удаляем маркер в конце, даже если произошла ошибка
Попытка
    УдалитьФайлы(ИмяФайлаБлокировки);
Исключение
    // Обработка ситуации, если файл не удалился
КонецПопытки;

Важный нюанс: основной недостаток этого метода заключается в риске «вечной блокировки». Если во время выполнения выгрузки произойдет аварийное завершение работы сервера или критическая ошибка, файл lock.txt останется в каталоге, и последующие запуски будут невозможны без ручного вмешательства.

Решение 2: Проверка состояния фоновых заданий

Если конфликт происходит именно между конкретным регламентным заданием и интерактивной обработкой, мы можем пойти по более технологичному пути. Вместо того чтобы проверять файлы на диске, мы проверим состояние потоков в самой 1С. Проанализируем ситуацию: если регламентное задание уже выполняется, обработке незачем даже пытаться обращаться к файловой системе.

Для реализации этого метода используем менеджер ФоновыеЗадания. Рассмотрим пример кода:


Отбор = Новый Структура;
Отбор.Вставить("Наименование", "ИмяВашегоРегламентногоЗадания");
Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);

АктивныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор);

Если АктивныеЗадания.Количество() > 0 Тогда
    ПоказатьОповещениеПользователя("Внимание", , "Выгрузка уже выполняется регламентным заданием.");
    Возврат;
КонецЕсли;

Этот способ является наиболее «чистым», так как он не оставляет мусора в файловой системе и опирается на внутренние механизмы платформы.

Решение 3: Атомарная проверка через переименование

Существует интересная хитрость, позволяющая проверить доступность файла средствами операционной системы. Операция переименования файла в Windows является атомарной. Если файл открыт другим приложением с монопольным доступом, ОС выдаст ошибку при попытке смены имени. Мы можем использовать это в блоке Попытка.

Выясним, как это работает на практике:

  1. Пробуем переименовать целевой файл, добавив к нему случайный суффикс.
  2. Если переименование прошло успешно, значит, файл свободен.
  3. Возвращаем имя файла обратно и приступаем к работе.
  4. Если возникло исключение — файл занят.

Функция ФайлСвободен(ПолноеИмяФайла)
    ФайлНаДиске = Новый Файл(ПолноеИмяФайла);
    Если Не ФайлНаДиске.Существует() Тогда
        Возврат Истина; // Файла нет, значит он свободен для создания
    КонецЕсли;

    ВременноеИмя = ПолноеИмяФайла + "_test_lock";
    Попытка
        ПереместитьФайл(ПолноеИмяФайла, ВременноеИмя);
        // Если дошли сюда, файл удалось захватить. Вернем его обратно.
        ПереместитьФайл(ВременноеИмя, ПолноеИмяФайла);
        Возврат Истина;
    Исключение
        Возврат Ложь;
    КонецПопытки;
КонецФункции

Решение 4: Использование временных файлов с последующим замещением

Чтобы максимально снизить вероятность столкновения двух процессов, разберем архитектурный подход «Теневого копирования». Вместо того чтобы писать данные напрямую в общий файл data.xml, каждый процесс (и фоновое задание, и обработка) должен выполнять следующие шаги:

  1. Создать уникальный временный файл во временном каталоге (используя ПолучитьИмяВременногоФайла()).
  2. Выполнить в него полную выгрузку всех данных. Это может занимать минуты, и в это время основной файл остается свободным.
  3. В самый последний момент, когда данные готовы, выполнить быструю операцию замены основного файла временным.

Замена файла происходит за миллисекунды. Вероятность того, что два процесса попытаются выполнить замену одновременно, стремится к нулю. Посмотрим на логику замещения:


ПутьКВременномуФайлу = ПолучитьИмяВременногоФайла("xml");
// ... тут код выгрузки во временный файл ...

Попытка
    ПереместитьФайл(ПутьКВременномуФайлу, ЦелевойПутьКФайлу);
Исключение
    Состояние("Не удалось обновить файл, так как он используется. Данные сохранены во временном хранилище.");
КонецПопытки;

Решение 5: Механизм программных семафоров в 1С

Если мы работаем в клиент-серверном варианте, мы можем использовать объект БлокировкаДанных или создать специальный Регистр сведений «ОчередьЗаданий». Перед началом работы процесс записывает в регистр информацию о том, что ресурс «ВыгрузкаФайлаА» занят.

Рассмотрим преимущества этого метода:

Подведение итогов

Для реализации требования автора темы (тихое уведомление внизу экрана без вылета программы) мы рекомендуем использовать комбинацию проверки фоновых заданий и атомарного переименования. Это обеспечит максимальную надежность.

Не забудьте использовать метод Состояние() или ПоказатьОповещениеПользователя() для вывода информационного сообщения пользователю. В отличие от Предупреждение() или Сообщить(), метод Состояние() выводит текст в нижней части экрана, что не блокирует работу пользователя и выглядит эстетично.

Резюме: всегда проектируйте механизмы обмена так, чтобы монопольный доступ к конечному файлу требовался на минимально короткое время, используя временные файлы-посредники. Это лучший способ избежать конфликтов совместного доступа в высоконагруженных системах 1С.

← На главную