При разработке механизмов автоматизации в «1С:Предприятие 8» (особенно в крупных и зрелых конфигурациях, таких как УПП 1.3) программисты часто сталкиваются с двумя классическими проблемами: неэффективным написанием кода, приводящим к критическому снижению производительности системы, и трудностями при первом запуске и отладке регламентных (фоновых) заданий. Разберем детально обе проблемы на практическом примере задачи по автоматическому отключению просроченных договоров контрагентов.
Рассмотрим исходный вариант кода, который изначально предложил разработчик на форуме:
Процедура ГЭС_ДоговораКонтрагентовНеДействующие() Экспорт
Выборка = Справочники.ДоговорыКонтрагентов.Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.Действует И Выборка.СрокДействия < ТекущаяДата() И НЕ Выборка.СрокДействия = Дата('00010101') И (Выборка.ГЭС_ВидДействияДоговора = Перечисления.ГЭС_ВидДейстивияДоговора.Бессрочный ИЛИ Выборка.ГЭС_ВидДействияДоговора = "") Тогда
ВыборкаОбъект = Выборка.ПолучитьОбъект();
ВыборкаОбъект.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.НеДействует;
ВыборкаОбъект.Записать();
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Этот код действительно «работает», если запустить его вручную на небольшой тестовой базе. Однако использовать его в реальной рабочей базе категорически запрещено. Метод Справочники.ДоговорыКонтрагентов.Выбрать() без параметров выполняет последовательное чтение всей таблицы справочника из СУБД в оперативную память сервера приложений.
Проанализируем последствия такого подхода:
ПолучитьОбъект() создает полноценный XML-объект в памяти со всеми его табличными частями и реквизитами. Делать это для элементов, которые не требуют изменений — крайне нерационально.Для понимания сути этой ошибки уместно использовать бытовую аналогию, прозвучавшую на форуме. Представьте, что вам нужно сварить гречневую кашу. Вместо того чтобы пойти в магазин и купить ровно один пакет гречки, вы покупаете абсолютно все крупы, которые есть на прилавках супермаркета, приносите их домой и перед кастрюлей начинаете перебирать: «Если это гречка — кладем в кастрюлю, иначе — выбрасываем». Именно так работает метод Выбрать() с последующей фильтрацией в кодовой конструкции Если...Тогда.
Чтобы избежать лишней нагрузки, мы должны делегировать фильтрацию данных самой СУБД. Программа должна получать из базы данных только ссылки на те договоры, которые гарантированно удовлетворяют нашим критериям отбора. Модифицируем код процедуры, используя объект Запрос:
Процедура ГЭС_ДоговораКонтрагентовНеДействующие() Экспорт
// Инициализируем запрос для предварительного отбора данных на уровне СУБД
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Договоры.Ссылка КАК Ссылка
|ИЗ
| Справочник.ДоговорыКонтрагентов КАК Договоры
|ГДЕ
| Договоры.ГЭС_СтатусДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_СтатусыДоговоров.Действует)
| И Договоры.СрокДействия < &ТекущаяДата
| И Договоры.СрокДействия <> ДАТАВРЕМЯ(1, 1, 1)
| И (Договоры.ГЭС_ВидДействияДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_ВидДейстивияДоговора.Бессрочный)
| ИЛИ Договоры.ГЭС_ВидДействияДоговора = ЗНАЧЕНИЕ(Перечисление.ГЭС_ВидДейстивияДоговора.ПустаяСсылка));";
// В качестве параметра передаем начало сегодняшнего дня, чтобы избежать проблем с часовыми поясами и секундами
Запрос.УстановитьПараметр("ТекущаяДата", НачалоДня(ТекущаяДата()));
Выборка = Запрос.Выполнить().Выбрать();
// Обходим исключительно те элементы, которые реально требуют модификации
Пока Выборка.Следующий() Цикл
// Получаем объект только для перезаписи
ДоговорОбъект = Выборка.Ссылка.ПолучитьОбъект();
ДоговорОбъект.ГЭС_СтатусДоговора = Перечисления.ГЭС_СтатусыДоговоров.НеДействует;
Попытка
// Включаем режим загрузки для обхода тяжелых проверок при записи, если это допустимо бизнес-логикой
ДоговорОбъект.ОбменДанными.Загрузка = Истина;
ДоговорОбъект.Записать();
Исключение
// Гарантируем, что ошибка в одном договоре не прервет выполнение всего регламентного задания
ЗаписьЖурналаРегистрации(
"РегламентноеЗадание.Договоры",
УровеньЖурналаРегистрации.Ошибка,
Метаданные.Справочники.ДоговорыКонтрагентов,
Выборка.Ссылка,
"Не удалось обновить статус договора. Описание ошибки: " + КраткоеПредставлениеОшибки(ИнформацияОбОшибке())
);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Рассмотрим подробнее преимущества оптимизированного варианта:
Попытка...Исключение предотвращает аварийное завершение фонового задания, если один из договоров окажется заблокирован другим пользователем в момент записи. Информация об ошибке корректно запишется в системный Журнал регистрации 1С.ОбменДанными.Загрузка = Истина отключает выполнение прикладных проверок в модуле объекта при записи, что ускоряет процесс в разы и предотвращает конфликты с правами доступа или неполным заполнением других реквизитов.Если код процедуры полностью отлажен и стабильно работает при ручном запуске через внешнюю обработку, но регламентное задание упорно молчит, необходимо последовательно проверить системные настройки платформы «1С:Предприятие».
Проанализируем основные причины, по которым планировщик заданий игнорирует запуск вашей процедуры:
В файловой версии базы данных (в отличие от клиент-серверной SQL-версии) нет выделенного процесса планировщика. Регламентные задания могут выполняться только в контексте какого-либо запущенного клиентского сеанса 1С. Если все пользователи закрыли программу, ни одно задание не запустится.
Для стабильной работы планировщика в файловой базе необходимо выделить отдельный технический компьютер (или виртуальную машину), запустить на нем сеанс «1С:Предприятия» под специальным пользователем и периодически инициировать обработку заданий с помощью метода ВыполнитьОбработкуЗаданий(). Также можно настроить встроенный обработчик ожидания:
ПодключитьОбработчикОжидания("ВыполнитьОбработкуЗаданий", 60);
Если база работает на сервере под управлением MS SQL или PostgreSQL, очень часто выполнение регламентных заданий блокируется администраторами принудительно (например, при создании копии базы, чтобы тестовая база не отправляла реальные письма или не выгружала данные на сайт).
Чтобы проверить этот параметр, выполните следующие шаги:
Регламентное задание может вызывать процедуры только из общих модулей с определенными свойствами. Откройте свойства вашего общего модуля в конфигураторе и проверьте флаги:
В типовых конфигурациях (особенно в УПП 1.3 на обычных формах) новые регламентные задания, созданные в конфигураторе, не всегда автоматически регистрируются в базе данных со статусом «Предопределенное». Для управления ими необходимо использовать специализированную обработку «Консоль заданий» (ее можно найти на диске ИТС или в составе дополнительных инструментов разработчика).
С помощью «Консоли заданий» выполните следующие действия:
Фоновые задания запускаются сервером обособленно, поэтому обычная точка останова в коде общего модуля по умолчанию не сработает. Чтобы зайти отладчиком в процедуру регламентного задания, выполните следующие шаги по настройке конфигуратора:
-debug в строке запуска службы в реестре Windows или конфигурационном файле под Linux). Если ключ отсутствует, отладка на сервере работать не будет.ФоновыеЗадания.Выполнить(). Конфигуратор автоматически перехватит управление, и вы сможете пошагово проанализировать выполнение кода.Применяя данные рекомендации, мы не только решим проблему с запуском фоновой задачи, но и сделаем работу системы стабильной и быстрой, исключив появление избыточных блокировок и зависаний сервера приложений.