Фоновое выполнение процедур в 1С 8.3: задания, ожидание, прогресс
Содержание
Чтобы запустить процедуру в фоне в 1С 8.3, вызовите на сервере ФоновыеЗадания.Выполнить("ОбщийМодуль.ИмяПроцедуры", МассивПараметров). Процедура должна быть экспортной и лежать в неглобальном общем модуле с флажком «Сервер». Клиент сразу получает управление, а задание работает в отдельном сеансе на сервере.
Это нужно, когда расчёт идёт минуты, а пользователь смотрит на «песочные часы» и не может работать. Ниже — рабочий код на чистой платформе, вариант через БСП с индикатором прогресса, регламентные задания и разбор ошибок.
Как 1С выполняет код в фоне
Вызов сервера в 1С всегда синхронный. Клиент отправил запрос — и ждёт ответа. Никакого «асинхронного вызова сервера» платформа не даёт, даже в 8.3 с конструкциями Асинх и Ждать.
Фоновое выполнение устроено иначе. Сервер создаёт фоновое задание — отдельный сеанс без интерфейса. Он выполняет указанный метод и завершается. Сеанс, который запустил задание, продолжает работу.
Что важно знать о фоновом сеансе:
- 👤 работает от имени того же пользователя, что и запустивший, с теми же правами;
- 🔁 параметры сеанса инициализируются заново, как при входе в программу;
- 🚫 форм, диалогов и вопросов пользователю нет — интерактивные методы недоступны;
- 📦 параметры передаются по значению, поэтому они должны сериализоваться: ссылки, примитивы, массивы, структуры. Данные формы и объекты формы передать нельзя;
- 🧵 транзакция запускающего сеанса на задание не распространяется: у задания свои транзакции.
Есть три способа запустить код в фоне:
- Напрямую через менеджер
ФоновыеЗадания— подходит для любой конфигурации. - Через подсистему «Длительные операции» БСП — есть в типовых (БП 3.0, ЗУП 3.1, УТ 11 и др.).
- Через регламентное задание — запуск по расписанию без участия пользователя.
Запуск процедуры в фоне через ФоновыеЗадания.Выполнить
Сигнатура метода:
ФоновыеЗадания.Выполнить(ИмяМетода, Параметры, Ключ, Наименование)
ИмяМетода— строка вида"ИмяМодуля.ИмяМетода".Параметры—Массивзначений в порядке параметров процедуры.Ключ— строка. Пока активно задание с тем же методом и ключом, второе такое же не запустится: платформа вызовет исключение.Наименование— любое описание, видно в списке заданий.
Метод возвращает объект ФоновоеЗадание. Сохраните его УникальныйИдентификатор — по нему вы потом найдёте задание.
Шаг 1. Общий модуль с процедурой
Создайте общий модуль, например ФоновыеРасчеты. Флажки: «Сервер» — включён, «Глобальный» — выключен, «Клиент» — выключен.
// Общий модуль ФоновыеРасчеты (Сервер)
Процедура ПересчитатьОстатки(Организация, АдресРезультата) Экспорт
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| НЕ Номенклатура.ЭтоГруппа";
Выборка = Запрос.Выполнить().Выбрать();
Всего = Выборка.Количество();
Обработано = 0;
Пока Выборка.Следующий() Цикл
// ... расчёт по позиции ...
Обработано = Обработано + 1;
Если Обработано % 100 = 0 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон("Обработано %1 из %2",
Формат(Обработано, "ЧГ=0"), Формат(Всего, "ЧГ=0"));
Сообщение.Сообщить();
КонецЕсли;
КонецЦикла;
Результат = Новый Структура("Обработано", Обработано);
ПоместитьВоВременноеХранилище(Результат, АдресРезультата);
КонецПроцедуры
Сообщения из фонового задания не появляются на экране сами. Они копятся в задании, и клиент забирает их отдельно (об этом ниже).
Шаг 2. Запуск из формы
В форме заведите два реквизита: ИдентификаторЗадания (тип УникальныйИдентификатор) и АдресРезультата (тип Строка).
&НаСервере
Процедура ЗапуститьРасчетНаСервере()
// Адрес хранилища привязан к форме и живёт, пока она открыта
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(Объект.Организация);
Параметры.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("ФоновыеРасчеты.ПересчитатьОстатки",
Параметры, "ПересчетОстатков", "Пересчёт остатков");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры
Ключ "ПересчетОстатков" защищает от двойного запуска. Если расчёт можно гонять параллельно по разным организациям, добавьте в ключ код организации.
Если адрес создать без идентификатора формы, данные в хранилище удалятся после ближайшего возврата с сервера. Результат задания тогда пропадёт.
Ожидание результата: опрос с клиента вместо блокировки
Задание запущено, клиент свободен. Теперь его нужно периодически спрашивать: «готово?». Для этого служит ПодключитьОбработчикОжидания.
&НаКлиенте
Процедура Рассчитать(Команда)
ЗапуститьРасчетНаСервере();
ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Ложь);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьЗадание()
ИнфоЗадания = СостояниеЗадания(ИдентификаторЗадания);
Для Каждого Текст Из ИнфоЗадания.Сообщения Цикл
Состояние(Текст); // строка состояния в нижней части окна
КонецЦикла;
Если ИнфоЗадания.Статус = "Активно" Тогда
Возврат;
КонецЕсли;
ОтключитьОбработчикОжидания("ПроверитьЗадание");
Если ИнфоЗадания.Статус = "Выполнено" Тогда
Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
ПоказатьПредупреждение(, СтрШаблон("Готово. Обработано позиций: %1",
Формат(Результат.Обработано, "ЧГ=0")));
Иначе
ПоказатьПредупреждение(, "Расчёт не выполнен: " + ИнфоЗадания.Ошибка);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
Ответ = Новый Структура("Статус, Ошибка, Сообщения", "", "", Новый Массив);
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Ответ.Статус = "НеНайдено";
Возврат Ответ;
КонецЕсли;
Сообщения = Задание.ПолучитьСообщенияПользователю(Истина);
Если Сообщения <> Неопределено Тогда
Для Каждого Сообщение Из Сообщения Цикл
Ответ.Сообщения.Добавить(Сообщение.Текст);
КонецЦикла;
КонецЕсли;
Если Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Ответ.Статус = "Активно";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Ответ.Статус = "Выполнено";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
Ответ.Статус = "Ошибка";
Ответ.Ошибка = ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
Иначе
Ответ.Статус = "Отменено";
КонецЕсли;
Возврат Ответ;
КонецФункции
Параметр Истина в ПолучитьСообщенияПользователю удаляет уже полученные сообщения. Без него при каждом опросе вы будете получать весь список заново.
Когда можно ждать на сервере
У задания есть метод ОжидатьЗавершенияВыполнения(Таймаут), у менеджера — ФоновыеЗадания.ОжидатьЗавершенияВыполнения(МассивЗаданий, Таймаут). Старое имя ОжидатьЗавершения в актуальных версиях платформы считается устаревшим.
Ожидание блокирует вызывающий сеанс. Поэтому из формы его вызывать бессмысленно — пользователь снова «висит». Оправданный сценарий один: распараллелить обработку. Сервер режет данные на порции, запускает по заданию на порцию и ждёт все сразу:
Задания = Новый Массив;
Для Каждого Порция Из Порции Цикл
Параметры = Новый Массив;
Параметры.Добавить(Порция);
Задания.Добавить(ФоновыеЗадания.Выполнить("ФоновыеРасчеты.ОбработатьПорцию", Параметры));
КонецЦикла;
ФоновыеЗадания.ОжидатьЗавершенияВыполнения(Задания, 3600);
Так делают в регламентных заданиях и в обработках, которые сами работают в фоне.
Асинх и Ждать — это не про сервер
В 8.3.18 появились асинхронные методы с Асинх и Ждать. Они упрощают код диалогов и работы с файлами на клиенте: вопрос пользователю, выбор файла, копирование. Вызов серверной процедуры они не делают асинхронным. Для тяжёлых расчётов путь один — фоновое задание.
Регламентные задания: фоновый запуск по расписанию
Регламентное задание — объект метаданных. Он связывает метод общего модуля с расписанием. Когда наступает время, сервер сам запускает этот метод как фоновое задание.
Как создать:
- В конфигураторе откройте ветку
Общие → Регламентные заданияи добавьте новое. - В поле «Имя метода» выберите экспортную процедуру серверного общего модуля.
- Включите «Использование» и задайте расписание.
- Флажок «Предопределённое» оставьте, если задание должно существовать в базе в одном экземпляре.
В типовых конфигурациях на БСП список заданий открывается из Администрирование → Обслуживание → Регламентные операции → Регламентные и фоновые задания. Там же видно, когда задание запускалось и чем закончилось.
Изменить расписание из кода:
Задание = РегламентныеЗадания.НайтиПредопределенное(Метаданные.РегламентныеЗадания.ЗагрузкаКурсов);
Расписание = Новый РасписаниеРегламентногоЗадания;
Расписание.ПериодПовтораДней = 1;
Расписание.ВремяНачала = Дата(1, 1, 1, 3, 0, 0); // каждый день в 03:00
Задание.Расписание = Расписание;
Задание.Использование = Истина;
Задание.Записать();
Запустить регламентное задание немедленно, не дожидаясь расписания:
ФоновыеЗадания.Выполнить(Задание.Метаданные.ИмяМетода, Задание.Параметры,
Строка(Задание.УникальныйИдентификатор), Задание.Наименование);
Где регламентные задания не сработают:
- 🖥️ файловая база — задания выполняются, только пока база открыта хотя бы в одном клиентском сеансе. Ночью при закрытой программе ничего не запустится;
- 🔒 клиент-серверная база — в свойствах информационной базы в консоли кластера есть флажок «Блокировка регламентных заданий включена». Его часто ставят на копиях баз, чтобы тестовая база не отправляла отчёты и не грузила обмены.
Длительные операции БСП: фон с индикатором прогресса
В типовых конфигурациях не пишите опрос вручную. Подсистема «Длительные операции» БСП уже умеет запускать задание, опрашивать его с нарастающим интервалом, показывать окно ожидания и прогресс.
Серверная часть формы:
&НаСервере
Функция НачатьРасчет()
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = "Пересчёт остатков";
Возврат ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения,
"ФоновыеРасчеты.РассчитатьОстатки", Объект.Организация);
КонецФункции
Клиентская часть:
&НаКлиенте
Процедура Рассчитать(Команда)
ДлительнаяОперация = НачатьРасчет();
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ПараметрыОжидания.ВыводитьПрогрессВыполнения = Истина;
Оповещение = Новый ОписаниеОповещения("РасчетЗавершен", ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, Оповещение, ПараметрыОжидания);
КонецПроцедуры
&НаКлиенте
Процедура РасчетЗавершен(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // пользователь отменил
КонецЕсли;
Если Результат.Статус = "Выполнено" Тогда
Данные = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);
// ... вывести результат ...
ИначеЕсли Результат.Статус = "Ошибка" Тогда
ПоказатьПредупреждение(, Результат.КраткоеПредставлениеОшибки);
КонецЕсли;
КонецПроцедуры
Здесь РассчитатьОстатки — экспортная функция. Её возвращаемое значение БСП сама положит во временное хранилище. Для процедуры есть пара ПараметрыВыполненияПроцедуры и ВыполнитьПроцедуру.
Прогресс внутри функции сообщают так:
ДлительныеОперации.СообщитьПрогресс(Процент, "Обработано " + Формат(Обработано, "ЧГ=0"));
Процент — число от 0 до 100. На клиенте он попадает в окно ожидания.
Перед использованием откройте общий модуль ДлительныеОперации в своей конфигурации и сверьте состав параметров: в старых версиях БСП был метод ВыполнитьВФоне с другой сигнатурой. Описание каждого метода — в комментарии над ним.
Какой способ фонового запуска выбрать
| Способ | Когда подходит | Ограничения |
|---|---|---|
ФоновыеЗадания.Выполнить + опрос | Своя конфигурация без БСП, нужна полная управляемость | Опрос, прогресс и ошибки пишете сами |
| БСП «Длительные операции» | Типовая или любая конфигурация на БСП | Зависит от версии БСП, сверяйте сигнатуры |
| Регламентное задание | Работа по расписанию: загрузки, обмены, закрытие периода | Нет пользователя, результат — только в данных и журнале |
ОжидатьЗавершенияВыполнения на сервере | Параллельная обработка порций внутри фонового задания | Блокирует вызывающий сеанс |
ПодключитьОбработчикОжидания без задания | Отложить лёгкое действие на клиенте | Это не фон: при вызове сервера клиент всё равно ждёт |
Правило простое: если операция дольше 5–10 секунд и её запускает пользователь — фоновое задание с опросом. Если её никто не запускает руками — регламентное задание.
Обработка ошибок и журнал регистрации в фоновых заданиях
Пользователь не видит ошибку фонового задания, пока его не спросят. Поэтому ошибки нужно и ловить, и записывать.
Схема в процедуре задания:
Процедура ЗагрузитьКурсы() Экспорт
Попытка
// ... основная работа ...
Исключение
ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("ФоновыеРасчеты.ЗагрузкаКурсов",
УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Повторный ВызватьИсключение обязателен. Без него задание получит статус «Завершено», и опрос сочтёт работу успешной.
Где смотреть следы:
- 📜 журнал регистрации —
Администрирование → Обслуживание → Журнал регистрациив типовых илиАдминистрирование → Журнал регистрациив конфигураторе. Отбор по своему имени события находит записи за секунды; - 🧾 список фоновых заданий — в БСП на форме «Регламентные и фоновые задания», вкладка с фоновыми заданиями показывает состояние и текст ошибки;
- 🖧 консоль кластера — для клиент-серверной базы видно активные фоновые сеансы и что они ждут.
Чтобы отменить зависшее задание из кода, найдите его через ФоновыеЗадания.НайтиПоУникальномуИдентификатору и вызовите Отменить(). Платформа прервёт выполнение и откатит незавершённую транзакцию.
Завершать фоновый сеанс из консоли кластера — крайняя мера. Незакоммиченные изменения откатятся, а если задание пишет данные порциями в разных транзакциях, часть порций останется записанной. Делайте это, когда понимаете, на каком этапе задание.
Не пишите в фоновом задании проверку «если ошибка — молча продолжить»: такие задания годами «работают», а данные расходятся.