Как выполнить процедуру сохранения данных при завершении работы обработки без формы в 1С

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

При разработке пакетных алгоритмов и длительных обработок в 1С часто применяется буферизация: данные собираются во временную таблицу значений в оперативной памяти и записываются в регистры или документы порциями (например, по 100 или 1000 строк). Однако возникает вопрос: как гарантированно сбросить остаток данных, если обработка завершает свою работу, особенно когда она запускается программно без открытия экранных форм?

Рассмотрим особенности работы платформы 1С с объектами в памяти и разберем надежные архитектурные решения для гарантированного выполнения финальных процедур.

Почему не работают стандартные события закрытия

Проанализируем архитектурные ограничения платформы 1С. В среде разработки существуют события ПередЗакрытием и ПриЗакрытии, однако они относятся исключительно к контексту управляемой или обычной формы. Если обработка создается и выполняется программно через Обработки.ИмяОбработки.Создать() или во внешнем соединении/фоновом задании, объект формы не инициализируется вовсе, а значит, события закрытия формы никогда не будут вызваны.

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

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

Вариант 1. Использование единого метода-фасада с блоком гарантированного завершения

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

Посмотрим на пример реализации модуля объекта:


// В модуле объекта обработки

Перем ТаблицаБуфера;

Процедура ИнициализироватьБуфер()
    ТаблицаБуфера = Новый ТаблицаЗначений;
    ТаблицаБуфера.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
    ТаблицаБуфера.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    ТаблицаБуфера.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
КонецПроцедуры

Процедура СброситьБуферВРегистр() Экспорт
    Если ТаблицаБуфера = Неопределено Или ТаблицаБуфера.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;

    Набор = РегистрыСведений.ОстаткиТоваров.СоздатьНаборЗаписей();
    // Логика переноса строк из ТаблицаБуфера в набор записей или через менеджер записи
    Для Каждого Стр Из ТаблицаБуфера Цикл
        Запись = Набор.Добавить();
        ЗаполнитьЗначенияСвойств(Запись, Стр);
    КонецЦикла;
    
    Набор.Записать(Ложь);
    ТаблицаБуфера.Очистить();
КонецПроцедуры

Процедура ДобавитьСтроку(Период, Номенклатура, Количество)
    Стр = ТаблицаБуфера.Добавить();
    Стр.Период = Период;
    Стр.Номенклатура = Номенклатура;
    Стр.Количество = Количество;

    Если ТаблицаБуфера.Количество() >= 100 Тогда
        СброситьБуферВРегистр();
    КонецЕсли;
КонецПроцедуры

Процедура ВыполнитьОбработку() Экспорт
    ИнициализироватьБуфер();
    
    Попытка
        // Основной цикл или обращение к источникам данных
        Пока ЕстьДанныеДляОбработки() Цикл
            // Выполняем полезную работу и добавляем строки
            ДобавитьСтроку(ТекущаяДата(), СсылкаНоменклатуры, 10);
        КонецЦикла;
        
        // Обязательный сброс оставшихся данных (менее 100 строк)
        СброситьБуферВРегистр();
        
    Исключение
        // При возникновении ошибки сбрасываем остаток или фиксируем состояние
        СброситьБуферВРегистр();
        
        // Повторно генерируем ошибку, чтобы вызывающий код узнал о сбое
        ВызватьИсключение;
    КонецПопытки;
КонецПроцедуры

Обратим внимание на оператор ВызватьИсключение в блоке Исключение. Он гарантирует, что мы не замаскируем системную или логическую ошибку, сохранив при этом уже накопленные порции данных.

Вариант 2. Явный вызов процедуры финализации вызывающим кодом

Если специфика алгоритма требует пошаговой работы с объектом из внешнего кода (например, вызовы методов выполняются из разных мест или по событиям), в обработке объявляется явная процедура завершения (например, ДоделатьРаботу() или Финализировать()).

Посмотрим, как организовать безопасный вызов такого объекта со стороны клиента или регламентного задания:


ОбработкаОбъект = Обработки.ЗагрузкаДанных.Создать();
Попытка
    ОбработкаОбъект.ВыполнитьШаг1();
    ОбработкаОбъект.ВыполнитьШаг2();
    
    // Явный вызов сброса остатков данных
    ОбработкаОбъект.СброситьБуферВРегистр();
Исключение
    // Гарантируем сброс буфера при аварийном завершении сценария
    ОбработкаОбъект.СброситьБуферВРегистр();
    ВызватьИсключение ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

Вариант 3. Использование порционных транзакций без буфера в памяти

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

Разберем шаги реализации:

  1. Открываем транзакцию с помощью НачатьТранзакцию().
  2. Выполняем непосредственную запись строки в базу данных.
  3. Счетчиком отслеживаем количество итераций: каждые N строк вызываем ЗафиксироватьТранзакцию() и снова НачатьТранзакцию().
  4. После выхода из основного цикла проверяем, открыта ли транзакция (через ТранзакцияАктивна()), и фиксируем оставшиеся строки.

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


Счетчик = 0;
НачатьТранзакцию();

Попытка
    Для Каждого СтрокаИсходника Из ИсточникДанных Цикл
        ЗаписатьСтрокуВРегистр(СтрокаИсходника);
        Счетчик = Счетчик + 1;
        
        Если Счетчик % 100 = 0 Тогда
            ЗафиксироватьТранзакцию();
            НачатьТранзакцию();
        КонецЕсли;
    КонецЦикла;
    
    Если ТранзакцияАктивна() Тогда
        ЗафиксироватьТранзакцию();
    КонецЕсли;
Исключение
    Если ТранзакцияАктивна() Тогда
        ОтменитьТранзакцию();
    КонецЕсли;
    ВызватьИсключение;
КонецПопытки;

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

← На главную