При разработке пакетных алгоритмов и длительных обработок в 1С часто применяется буферизация: данные собираются во временную таблицу значений в оперативной памяти и записываются в регистры или документы порциями (например, по 100 или 1000 строк). Однако возникает вопрос: как гарантированно сбросить остаток данных, если обработка завершает свою работу, особенно когда она запускается программно без открытия экранных форм?
Рассмотрим особенности работы платформы 1С с объектами в памяти и разберем надежные архитектурные решения для гарантированного выполнения финальных процедур.
Проанализируем архитектурные ограничения платформы 1С. В среде разработки существуют события ПередЗакрытием и ПриЗакрытии, однако они относятся исключительно к контексту управляемой или обычной формы. Если обработка создается и выполняется программно через Обработки.ИмяОбработки.Создать() или во внешнем соединении/фоновом задании, объект формы не инициализируется вовсе, а значит, события закрытия формы никогда не будут вызваны.
В модуле объекта обработки (в отличие от некоторых объектно-ориентированных языков) отсутствуют встроенные деструкторы или системные события вроде «ПриУничтожении». Платформа 1С управляет временем жизни объектов с помощью автоматического сборщика мусора и подсчета ссылок. Когда переменная обработки выходит из области видимости, объект просто освобождается в оперативной памяти без вызова каких-либо обработчиков в коде конфигурации.
Поэтому перехватить момент автоматической очистки памяти невозможно, и задачу финализации необходимо решать на уровне архитектуры вызовов методов.
Разберем самый надежный подход: инкапсулировать весь процесс в единый экспортный метод модуля объекта (например, ВыполнитьОбработку()). Вызывающая сторона не должна напрямую управлять отдельными внутренними шагами. Внутри этого метода организуется цикл обработки с блоком Попытка ... Исключение, а в конце алгоритма всегда вызывается сброс накопленного буфера.
Посмотрим на пример реализации модуля объекта:
// В модуле объекта обработки
Перем ТаблицаБуфера;
Процедура ИнициализироватьБуфер()
ТаблицаБуфера = Новый ТаблицаЗначений;
ТаблицаБуфера.Колонки.Добавить("Период", Новый ОписаниеТипов("Дата"));
ТаблицаБуфера.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТаблицаБуфера.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число"));
КонецПроцедуры
Процедура СброситьБуферВРегистр() Экспорт
Если ТаблицаБуфера = Неопределено Или ТаблицаБуфера.Количество() = 0 Тогда
Возврат;
КонецЕсли;
Набор = РегистрыСведений.ОстаткиТоваров.СоздатьНаборЗаписей();
// Логика переноса строк из ТаблицаБуфера в набор записей или через менеджер записи
Для Каждого Стр Из ТаблицаБуфера Цикл
Запись = Набор.Добавить();
ЗаполнитьЗначенияСвойств(Запись, Стр);
КонецЦикла;
Набор.Записать(Ложь);
ТаблицаБуфера.Очистить();
КонецПроцедуры
Процедура ДобавитьСтроку(Период, Номенклатура, Количество)
Стр = ТаблицаБуфера.Добавить();
Стр.Период = Период;
Стр.Номенклатура = Номенклатура;
Стр.Количество = Количество;
Если ТаблицаБуфера.Количество() >= 100 Тогда
СброситьБуферВРегистр();
КонецЕсли;
КонецПроцедуры
Процедура ВыполнитьОбработку() Экспорт
ИнициализироватьБуфер();
Попытка
// Основной цикл или обращение к источникам данных
Пока ЕстьДанныеДляОбработки() Цикл
// Выполняем полезную работу и добавляем строки
ДобавитьСтроку(ТекущаяДата(), СсылкаНоменклатуры, 10);
КонецЦикла;
// Обязательный сброс оставшихся данных (менее 100 строк)
СброситьБуферВРегистр();
Исключение
// При возникновении ошибки сбрасываем остаток или фиксируем состояние
СброситьБуферВРегистр();
// Повторно генерируем ошибку, чтобы вызывающий код узнал о сбое
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Обратим внимание на оператор ВызватьИсключение в блоке Исключение. Он гарантирует, что мы не замаскируем системную или логическую ошибку, сохранив при этом уже накопленные порции данных.
Если специфика алгоритма требует пошаговой работы с объектом из внешнего кода (например, вызовы методов выполняются из разных мест или по событиям), в обработке объявляется явная процедура завершения (например, ДоделатьРаботу() или Финализировать()).
Посмотрим, как организовать безопасный вызов такого объекта со стороны клиента или регламентного задания:
ОбработкаОбъект = Обработки.ЗагрузкаДанных.Создать();
Попытка
ОбработкаОбъект.ВыполнитьШаг1();
ОбработкаОбъект.ВыполнитьШаг2();
// Явный вызов сброса остатков данных
ОбработкаОбъект.СброситьБуферВРегистр();
Исключение
// Гарантируем сброс буфера при аварийном завершении сценария
ОбработкаОбъект.СброситьБуферВРегистр();
ВызватьИсключение ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
Рассмотрим альтернативный паттерн, когда данные не накапливаются в таблице значений в оперативной памяти, а сразу пишутся в целевой регистр или таблицу базы данных с использованием транзакций. Это предотвращает утечки памяти и снижает риск потери данных при внезапном сбое процесса rphost.
Разберем шаги реализации:
НачатьТранзакцию().N строк вызываем ЗафиксироватьТранзакцию() и снова НачатьТранзакцию().ТранзакцияАктивна()), и фиксируем оставшиеся строки.Посмотрим на пример кода с транзакциями:
Счетчик = 0;
НачатьТранзакцию();
Попытка
Для Каждого СтрокаИсходника Из ИсточникДанных Цикл
ЗаписатьСтрокуВРегистр(СтрокаИсходника);
Счетчик = Счетчик + 1;
Если Счетчик % 100 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
Если ТранзакцияАктивна() Тогда
ЗафиксироватьТранзакцию();
КонецЕсли;
Исключение
Если ТранзакцияАктивна() Тогда
ОтменитьТранзакцию();
КонецЕсли;
ВызватьИсключение;
КонецПопытки;
Таким образом, для пакетных обработок без экранных форм правильным решением является отказ от попыток найти деструктор объекта и переход к явной финализации через фасадные методы, блоки Попытка ... Исключение и порционную фиксацию транзакций.