Разработчики в среде «1С:Предприятие 8» регулярно сталкиваются с ситуацией, когда запущенное фоновое задание начинает потреблять избыточные ресурсы сервера или просто зависает. В этот момент администратор или встроенный алгоритм вызывает метод ФоновоеЗадание.Отменить() (например, через консоль заданий). Однако, к удивлению многих, задание не прекращает свою работу мгновенно. Более того, в некоторых случаях оно может беспрепятственно дойти до самого конца и завершиться со статусом «Успешно».
В этой статье мы подробно проанализируем внутреннее устройство платформы «1С:Предприятие», разберем причины такого «непослушного» поведения механизма отмены и рассмотрим практические методы решения проблемы как на уровне разработки кода, так и на уровне системного администрирования.
Для понимания проблемы важно осознать ключевой принцип: на уровне ядра платформы 1С вызов метода Отменить() не выполняет жесткого принудительного завершения потока операционной системы. Платформа умышленно избегает аналогов жесткого убийства процесса (таких как Thread.Abort в .NET или команда kill -9 в операционных системах Linux).
Если бы платформа убивала поток операционной системы мгновенно, это привело бы к катастрофическим последствиям для всего кластера 1С:
rphost.exe, что вызвало бы аварийное завершение сеансов других пользователей, работающих в этом же процессе.Вместо этого разработчики платформы реализовали концепцию кооперативной отмены (cooperative cancellation). Работает она по следующему алгоритму:
Отменить() для конкретного фонового задания менеджер кластера устанавливает специальный внутренний флаг — «признак бесполезности» (или флаг отмены).Официальная позиция технологической платформы «1С» такова: досрочное завершение фонового задания после отправки команды отмены не гарантируется на 100%. Всегда существует вероятность того, что управление кодом не сможет вернуться в точку проверки этого флага.
Проанализируем основные сценарии, при которых «признак бесполезности» устанавливается, но фоновое задание продолжает успешно работать или зависает в памяти.
А. Выполнение кода вне интерпретатора 1С (Внешние компоненты и COM-объекты)
Если внутри фонового задания выполняется код внешней компоненты, происходит обращение к сторонней DLL-библиотеке или вызывается метод COM-объекта (например, чтение тяжелого файла Excel через ActiveX), управление физически покидает виртуальную машину 1С. В этот момент поток 1С замирает и ожидает ответа от сторонней библиотеки. Пока внешняя программа не завершит свое действие и не вернет управление обратно интерпретатору 1С, платформа физически не сможет проверить флаг отмены. Если внешняя операция завершается успешно, управление возвращается в 1С, и задание может прерваться только на следующей строке кода. Но ключевое стороннее действие к этому моменту уже выполнено.
Б. Длительные запросы к СУБД
Аналогичная ситуация возникает при отправке тяжелого, неоптимального SQL-запроса к базе данных. Поток процесса rphost.exe уходит в режим ожидания ответа по сетевому TCP-сокету от сервера СУБД (MS SQL Server, PostgreSQL и др.). Пока сервер СУБД строит план и выполняет выборку миллионов записей, платформа 1С «спит». Команда Отменить() не прерывает физический запрос на СУБД мгновенно. Задание прервется только тогда, когда СУБД вернет данные, и интерпретатор 1С перейдет к следующей строке кода.
В. Использование неделимых платформенных методов
Некоторые встроенные функции платформы написаны на языке C++ и выполняются как единая транзакционная или вычислительная операция на уровне ядра. К таким операциям относятся:
ЗаписьZipФайла и ЧтениеZipФайла.Внутри этих методов встроенный цикл проверки флага отмены зачастую отсутствует. Прерывание произойдет только после того, как метод полностью завершит обработку данных.
Г. Массивные транзакции базы данных
Если фоновое задание выполняет массовое создание или изменение документов в рамках одной транзакции (между операторами НачатьТранзакцию() и ЗафиксироватьТранзакцию()), вызов Отменить() инициирует немедленный откат транзакции на уровне СУБД. Однако откат изменений сотен тысяч строк в СУБД — это тяжелая дисковая и вычислительная операция. На это СУБД может потребоваться огромное количество времени (иногда даже больше времени, чем ушло на саму запись). В этот период задание визуально отображается в консоли как активное, создавая полную иллюзию игнорирования команды отмены.
Если вы разрабатываете длительный алгоритм, который будет выполняться в фоновом режиме, вы должны самостоятельно спроектировать его архитектуру так, чтобы он был чувствителен к отмене.
Рассмотрим пример создания безопасного цикла обработки данных с использованием встроенных механизмов платформы. Мы должны периодически проверять, активен ли еще наш сеанс фонового задания.
// Пример безопасной обработки большого массива данных в фоновом задании
Процедура ВыполнитьДлительнуюОбработкуДанных(МассивОбъектов) Экспорт
КоличествоЭлементов = МассивОбъектов.Количество();
Для Индекс = 0 По КоличествоЭлементов - 1 Цикл
// Каждые 100 итераций проверяем состояние фонового задания
Если Индекс % 100 = 0 Тогда
// Получаем текущее фоновое задание этого сеанса
ТекущееЗадание = ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание();
// Если задание было отменено извне, платформа вернет Неопределено
// или состояние задания изменится
Если ТекущееЗадание = Неопределено Или ТекущееЗадание.Состояние <> СостояниеФоновогоЗадания.Активно Тогда
ВызватьИсключение "Выполнение фонового задания было прервано вызывающей системой.";
КонецЕсли;
КонецЕсли;
// Выполняем полезную работу
ОбработатьЭлемент(МассивОбъектов[Индекс]);
КонецЦикла;
КонецПроцедуры
Рассмотрим также подход, используемый в Библиотеке стандартных подсистем (БСП). В БСП механизм длительных операций не полагается исключительно на платформенное поведение ФоновоеЗадание.Отменить(). Вместо этого БСП использует программную обертку:
ДлительныеОперации.ПрекратитьВыполнениеЕслиЗапрошеноОтмену().Пример использования механизмов БСП в своем коде:
// Пример интеграции с механизмами отмены БСП
Процедура ВыгрузитьДанныеБСП(ПараметрыВыгрузки, АдресРезультата) Экспорт
ПараметрыВыполнения = Новый Структура;
ПараметрыВыполнения.Вставить("ИдентификаторЗадания", ПолучитьИдентификаторТекущегоЗадания());
Пока ЕстьДанныеДляОбработки() Цикл
// Интерактивная проверка отмены через БСП
// Если пользователь нажал "Отмена" в интерфейсе, БСП прервет выполнение здесь
ДлительныеОперации.ПроверитьВыполнениеИПрерватьЕслиЗапрошено(ПараметрыВыполнения.ИдентификаторЗадания);
ОбработатьПорциюДанных();
КонецЦикла;
КонецПроцедуры
Функция ПолучитьИдентификаторТекущегоЗадания()
ТекущееЗадание = ФоновыеЗадания.ПолучитьТекущееФоновоеЗадание();
Возврат ?(ТекущееЗадание = Неопределено, Новый УникальныйИдентификатор, ТекущееЗадание.УникальныйИдентификатор);
КонецФункции
Иногда возникает ситуация рассинхронизации внутренних слоев СУБД, планировщика 1С и сеансов пользователей. В консоли кластера фоновое задание висит в состоянии «Выполняется», при попытке его отмены выводится ошибка: «Задание не выполняется, его нельзя отменить», но при этом в диспетчере задач рабочий процесс продолжает грузить процессор на 100%.
Вместо грубого перезапуска всей службы сервера 1С («reboot» сервера), который прервет работу всех пользователей организации, проанализируем и применим деликатный административный метод изоляции зависшего потока:
rphost.exe с PID 1248).rphost.rphost.exe с PID 1248 (через «Завершить процесс» или команду kill -9 1248).Такой подход гарантирует точечное решение проблемы «зависшего» фонового задания без прерывания бизнес-процессов компании и работы остальных сотрудников.
Подводя итоги нашего технического анализа, зафиксируем ключевые правила работы с отменой фоновых заданий:
ФоновоеЗадание.Отменить() работает по принципу кооперативной отмены. Он лишь просит задание завершиться при первой безопасной возможности.rphost в режим вытеснения с его последующим точечным уничтожением по PID в операционной системе.