Как исключить конфликт при одновременном скачивании и обработке файлов с FTP в 1С?

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

В практике разработки на платформе 1С:Предприятие 8.3 часто встречается архитектурная задача: одно регламентное задание должно скачать файл с внешнего ресурса (например, FTP-сервера), а второе — обработать его и загрузить данные в информационную базу. Однако здесь кроется классическая ловушка «состояния гонки» (race condition). Если второе задание запускается в тот момент, когда первое еще не закончило процесс копирования, оно может попытаться прочитать недозаписанный («битый») файл. В результате возникают ошибки формата XML, создаются файлы размером 0 Кб, или данные загружаются лишь частично.

В данной статье мы подробно разберем несколько стратегий решения этой проблемы: от простых «семафоров» до профессиональных методов атомарного переименования файлов.

Проблема: Почему возникают «битые» файлы?

Разберем ситуацию на примере. Регламентное задание №1 вызывает метод ФТП.Получить(). Операционная система сразу создает целевой файл в локальном каталоге, но наполнение его данными происходит постепенно. В это же время регламентное задание №2 проверяет наличие файла в папке. Видя, что файл существует, оно мгновенно начинает его чтение. Поскольку файл еще заблокирован процессом скачивания или просто не заполнен до конца, 1С выдает ошибку доступа или считывает пустую структуру. Чтобы этого избежать, нам нужно гарантировать, что обработка начнется только после полного освобождения файла первым процессом.

Решение 1: Использование «файла-семафора»

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

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

Для реализации проверки существования мы будем использовать объект Файл и его метод Существует(). Проанализируем пример логики для первого задания:


// Регламентное задание 1: Скачивание
ПутьКЛоку = КаталогВременныхФайлов() + "ftp_download.lock";
ФайлЛока = Новый Файл(ПутьКЛоку);

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

Попытка
    ФТП.Получить(ИмяФайлаНаСервере, ПутьЛокальный);
    // После успешного получения удаляем семафор
    УдалитьФайлы(ПутьКЛоку);
Исключение
    // В случае ошибки тоже важно удалить семафор, чтобы не заблокировать очередь
    УдалитьФайлы(ПутьКЛоку);
    ЗаписатьЛогСобытия("Ошибка скачивания: " + ОписаниеОшибки());
КонецПопытки;

Решение 2: Монопольный доступ через ФайловыйПоток

Более надежный способ на уровне операционной системы — попытка открыть файл в режиме, запрещающем совместный доступ. Если файл еще скачивается методом ФТП.Получить(), ОС не позволит другому процессу открыть его монопольно. Мы можем использовать объект ФайловыйПоток.

Рассмотрим код, который может использовать второе задание для проверки доступности файла:


// Регламентное задание 2: Обработка
ИмяФайла = "data.xml";
ПутьКФайлу = "C:\FTP_Exchange\" + ИмяФайла;

Попытка
    // Пытаемся открыть файл исключительно для себя
    Поток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть, ДоступКФайлу.Чтение, ВозможностьСовместногоДоступа.Нет);
    // Если код прошел сюда, значит файл не занят другим процессом
    Поток.Закрыть(); 
Исключение
    // Файл еще занят скачиванием или другим процессом
    Возврат; // Выходим и ждем следующего запуска задания
КонецПопытки;

// Начинаем безопасную обработку файла
ОбработатьФайл(ПутьКФайлу);

Решение 3: Метод временного расширения (Атомарное переименование)

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

  1. Первое задание скачивает файл под временным именем, например, order.xml.tmp.
  2. Метод ФТП.Получить() работает с этим временным расширением. Второе задание настроено игнорировать файлы с расширением .tmp.
  3. Как только скачивание завершено, мы используем команду ПереместитьФайл(), чтобы сменить имя на order.xml.

Важно понимать, что операция переименования (перемещения) внутри одного логического диска в файловых системах (NTFS/Linux) происходит мгновенно (атомарно). Файл либо имеет старое имя, либо новое, промежуточного состояния «полупереименован» не существует.

Посмотрим на пример кода:


// В первом задании
ИмяФайлаИтоговое = "order.xml";
ИмяФайлаВременное = ИмяФайлаИтоговое + ".tmp";

ФТП.Получить(ИмяФайлаИтоговое, ПутьККаталогу + ИмяФайлаВременное);

// Переименовываем только после успешного завершения скачивания
ПереместитьФайл(ПутьККаталогу + ИмяФайлаВременное, ПутьККаталогу + ИмяФайлаИтоговое);

Решение 4: Разделение каталогов («Входящие» и «Готовые»)

Аналогично методу с переименованием, мы можем использовать разные папки. Проанализируем этот подход:

  1. Создаем две папки: /Temp/ и /Ready/.
  2. Задание №1 скачивает всё в папку /Temp/.
  3. После завершения скачивания файл перемещается в папку /Ready/.
  4. Задание №2 сканирует только папку /Ready/.

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

Решение 5: Объединение в одну цепочку (Самое простое решение)

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

Разберем пример структуры такого задания:


Процедура ВыполнитьОбменСFTP() Экспорт
    
    Если СкачатьФайлССервера() Тогда
        // Если скачивание прошло успешно и файл гарантированно закрыт
        ОбработатьСкачанныйФайл();
    Иначе
        ЗаписатьЛогСобытия("Скачивание не удалось, обработка пропущена.");
    КонецЕсли;
    
КонецПроцедуры

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

Дополнительный контроль: Проверка размера файла

В некоторых случаях, когда мы не можем управлять процессом записи (например, файл пишет сторонняя программа), можно использовать проверку стабильности размера. Мы считываем размер файла через Файл.Размер(), делаем паузу в 1-2 секунды и считываем снова. Если размер не изменился, велика вероятность, что запись завершена. Однако этот метод менее надежен, чем блокировка или переименование, так как при медленном интернете размер может не меняться несколько секунд, хотя закачка еще идет.

Резюмируем: для стабильной работы обмена в 1С 8.3 рекомендуется использовать метод атомарного переименования (Solution 3) или объединение задач в одну цепочку (Solution 5). Это гарантирует целостность данных и отсутствие ошибок доступа к файлам.

← На главную