Фоновое выполнение процедур в 1С 8.3: задания, ожидание, прогресс

· 9 мин

Содержание

Чтобы запустить процедуру в фоне в 1С 8.3, вызовите на сервере ФоновыеЗадания.Выполнить("ОбщийМодуль.ИмяПроцедуры", МассивПараметров). Процедура должна быть экспортной и лежать в неглобальном общем модуле с флажком «Сервер». Клиент сразу получает управление, а задание работает в отдельном сеансе на сервере.

Это нужно, когда расчёт идёт минуты, а пользователь смотрит на «песочные часы» и не может работать. Ниже — рабочий код на чистой платформе, вариант через БСП с индикатором прогресса, регламентные задания и разбор ошибок.

Как 1С выполняет код в фоне

Вызов сервера в 1С всегда синхронный. Клиент отправил запрос — и ждёт ответа. Никакого «асинхронного вызова сервера» платформа не даёт, даже в 8.3 с конструкциями Асинх и Ждать.

Фоновое выполнение устроено иначе. Сервер создаёт фоновое задание — отдельный сеанс без интерфейса. Он выполняет указанный метод и завершается. Сеанс, который запустил задание, продолжает работу.

Что важно знать о фоновом сеансе:

  • 👤 работает от имени того же пользователя, что и запустивший, с теми же правами;
  • 🔁 параметры сеанса инициализируются заново, как при входе в программу;
  • 🚫 форм, диалогов и вопросов пользователю нет — интерактивные методы недоступны;
  • 📦 параметры передаются по значению, поэтому они должны сериализоваться: ссылки, примитивы, массивы, структуры. Данные формы и объекты формы передать нельзя;
  • 🧵 транзакция запускающего сеанса на задание не распространяется: у задания свои транзакции.

Есть три способа запустить код в фоне:

  1. Напрямую через менеджер ФоновыеЗадания — подходит для любой конфигурации.
  2. Через подсистему «Длительные операции» БСП — есть в типовых (БП 3.0, ЗУП 3.1, УТ 11 и др.).
  3. Через регламентное задание — запуск по расписанию без участия пользователя.

Запуск процедуры в фоне через ФоновыеЗадания.Выполнить

Сигнатура метода:

ФоновыеЗадания.Выполнить(ИмяМетода, Параметры, Ключ, Наименование)
  • ИмяМетода — строка вида "ИмяМодуля.ИмяМетода".
  • Параметры — Массив значений в порядке параметров процедуры.
  • Ключ — строка. Пока активно задание с тем же методом и ключом, второе такое же не запустится: платформа вызовет исключение.
  • Наименование — любое описание, видно в списке заданий.

Метод возвращает объект ФоновоеЗадание. Сохраните его УникальныйИдентификатор — по нему вы потом найдёте задание.

Шаг 1. Общий модуль с процедурой

Создайте общий модуль, например ФоновыеРасчеты. Флажки: «Сервер» — включён, «Глобальный» — выключен, «Клиент» — выключен.

// Общий модуль ФоновыеРасчеты (Сервер)
Процедура ПересчитатьОстатки(Организация, АдресРезультата) Экспорт

    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ
    |   Номенклатура.Ссылка КАК Ссылка
    |ИЗ
    |   Справочник.Номенклатура КАК Номенклатура
    |ГДЕ
    |   НЕ Номенклатура.ЭтоГруппа";
    Выборка = Запрос.Выполнить().Выбрать();

    Всего = Выборка.Количество();
    Обработано = 0;

    Пока Выборка.Следующий() Цикл
        // ... расчёт по позиции ...
        Обработано = Обработано + 1;
        Если Обработано % 100 = 0 Тогда
            Сообщение = Новый СообщениеПользователю;
            Сообщение.Текст = СтрШаблон("Обработано %1 из %2",
                Формат(Обработано, "ЧГ=0"), Формат(Всего, "ЧГ=0"));
            Сообщение.Сообщить();
        КонецЕсли;
    КонецЦикла;

    Результат = Новый Структура("Обработано", Обработано);
    ПоместитьВоВременноеХранилище(Результат, АдресРезультата);

КонецПроцедуры

Сообщения из фонового задания не появляются на экране сами. Они копятся в задании, и клиент забирает их отдельно (об этом ниже).

Шаг 2. Запуск из формы

В форме заведите два реквизита: ИдентификаторЗадания (тип УникальныйИдентификатор) и АдресРезультата (тип Строка).

&НаСервере
Процедура ЗапуститьРасчетНаСервере()

    // Адрес хранилища привязан к форме и живёт, пока она открыта
    АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);

    Параметры = Новый Массив;
    Параметры.Добавить(Объект.Организация);
    Параметры.Добавить(АдресРезультата);

    Задание = ФоновыеЗадания.Выполнить("ФоновыеРасчеты.ПересчитатьОстатки",
        Параметры, "ПересчетОстатков", "Пересчёт остатков");
    ИдентификаторЗадания = Задание.УникальныйИдентификатор;

КонецПроцедуры

Ключ "ПересчетОстатков" защищает от двойного запуска. Если расчёт можно гонять параллельно по разным организациям, добавьте в ключ код организации.

Если адрес создать без идентификатора формы, данные в хранилище удалятся после ближайшего возврата с сервера. Результат задания тогда пропадёт.

Ожидание результата: опрос с клиента вместо блокировки

Задание запущено, клиент свободен. Теперь его нужно периодически спрашивать: «готово?». Для этого служит ПодключитьОбработчикОжидания.

&НаКлиенте
Процедура Рассчитать(Команда)
    ЗапуститьРасчетНаСервере();
    ПодключитьОбработчикОжидания("ПроверитьЗадание", 2, Ложь);
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьЗадание()

    ИнфоЗадания = СостояниеЗадания(ИдентификаторЗадания);

    Для Каждого Текст Из ИнфоЗадания.Сообщения Цикл
        Состояние(Текст); // строка состояния в нижней части окна
    КонецЦикла;

    Если ИнфоЗадания.Статус = "Активно" Тогда
        Возврат;
    КонецЕсли;

    ОтключитьОбработчикОжидания("ПроверитьЗадание");

    Если ИнфоЗадания.Статус = "Выполнено" Тогда
        Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
        ПоказатьПредупреждение(, СтрШаблон("Готово. Обработано позиций: %1",
            Формат(Результат.Обработано, "ЧГ=0")));
    Иначе
        ПоказатьПредупреждение(, "Расчёт не выполнен: " + ИнфоЗадания.Ошибка);
    КонецЕсли;

КонецПроцедуры

&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)

    Ответ = Новый Структура("Статус, Ошибка, Сообщения", "", "", Новый Массив);
    Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);

    Если Задание = Неопределено Тогда
        Ответ.Статус = "НеНайдено";
        Возврат Ответ;
    КонецЕсли;

    Сообщения = Задание.ПолучитьСообщенияПользователю(Истина);
    Если Сообщения <> Неопределено Тогда
        Для Каждого Сообщение Из Сообщения Цикл
            Ответ.Сообщения.Добавить(Сообщение.Текст);
        КонецЦикла;
    КонецЕсли;

    Если Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
        Ответ.Статус = "Активно";
    ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
        Ответ.Статус = "Выполнено";
    ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
        Ответ.Статус = "Ошибка";
        Ответ.Ошибка = ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
    Иначе
        Ответ.Статус = "Отменено";
    КонецЕсли;

    Возврат Ответ;

КонецФункции

Параметр Истина в ПолучитьСообщенияПользователю удаляет уже полученные сообщения. Без него при каждом опросе вы будете получать весь список заново.

Когда можно ждать на сервере

У задания есть метод ОжидатьЗавершенияВыполнения(Таймаут), у менеджера — ФоновыеЗадания.ОжидатьЗавершенияВыполнения(МассивЗаданий, Таймаут). Старое имя ОжидатьЗавершения в актуальных версиях платформы считается устаревшим.

Ожидание блокирует вызывающий сеанс. Поэтому из формы его вызывать бессмысленно — пользователь снова «висит». Оправданный сценарий один: распараллелить обработку. Сервер режет данные на порции, запускает по заданию на порцию и ждёт все сразу:

Задания = Новый Массив;
Для Каждого Порция Из Порции Цикл
    Параметры = Новый Массив;
    Параметры.Добавить(Порция);
    Задания.Добавить(ФоновыеЗадания.Выполнить("ФоновыеРасчеты.ОбработатьПорцию", Параметры));
КонецЦикла;
ФоновыеЗадания.ОжидатьЗавершенияВыполнения(Задания, 3600);

Так делают в регламентных заданиях и в обработках, которые сами работают в фоне.

Асинх и Ждать — это не про сервер

В 8.3.18 появились асинхронные методы с Асинх и Ждать. Они упрощают код диалогов и работы с файлами на клиенте: вопрос пользователю, выбор файла, копирование. Вызов серверной процедуры они не делают асинхронным. Для тяжёлых расчётов путь один — фоновое задание.

Регламентные задания: фоновый запуск по расписанию

Регламентное задание — объект метаданных. Он связывает метод общего модуля с расписанием. Когда наступает время, сервер сам запускает этот метод как фоновое задание.

Как создать:

  1. В конфигураторе откройте ветку Общие → Регламентные задания и добавьте новое.
  2. В поле «Имя метода» выберите экспортную процедуру серверного общего модуля.
  3. Включите «Использование» и задайте расписание.
  4. Флажок «Предопределённое» оставьте, если задание должно существовать в базе в одном экземпляре.

В типовых конфигурациях на БСП список заданий открывается из Администрирование → Обслуживание → Регламентные операции → Регламентные и фоновые задания. Там же видно, когда задание запускалось и чем закончилось.

Изменить расписание из кода:

Задание = РегламентныеЗадания.НайтиПредопределенное(Метаданные.РегламентныеЗадания.ЗагрузкаКурсов);

Расписание = Новый РасписаниеРегламентногоЗадания;
Расписание.ПериодПовтораДней = 1;
Расписание.ВремяНачала = Дата(1, 1, 1, 3, 0, 0); // каждый день в 03:00

Задание.Расписание = Расписание;
Задание.Использование = Истина;
Задание.Записать();

Запустить регламентное задание немедленно, не дожидаясь расписания:

ФоновыеЗадания.Выполнить(Задание.Метаданные.ИмяМетода, Задание.Параметры,
    Строка(Задание.УникальныйИдентификатор), Задание.Наименование);

Где регламентные задания не сработают:

  • 🖥️ файловая база — задания выполняются, только пока база открыта хотя бы в одном клиентском сеансе. Ночью при закрытой программе ничего не запустится;
  • 🔒 клиент-серверная база — в свойствах информационной базы в консоли кластера есть флажок «Блокировка регламентных заданий включена». Его часто ставят на копиях баз, чтобы тестовая база не отправляла отчёты и не грузила обмены.
Диагностика: выберите симптом

Длительные операции БСП: фон с индикатором прогресса

В типовых конфигурациях не пишите опрос вручную. Подсистема «Длительные операции» БСП уже умеет запускать задание, опрашивать его с нарастающим интервалом, показывать окно ожидания и прогресс.

Серверная часть формы:

&НаСервере
Функция НачатьРасчет()
    ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор);
    ПараметрыВыполнения.НаименованиеФоновогоЗадания = "Пересчёт остатков";
    Возврат ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения,
        "ФоновыеРасчеты.РассчитатьОстатки", Объект.Организация);
КонецФункции

Клиентская часть:

&НаКлиенте
Процедура Рассчитать(Команда)
    ДлительнаяОперация = НачатьРасчет();
    ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
    ПараметрыОжидания.ВыводитьПрогрессВыполнения = Истина;
    Оповещение = Новый ОписаниеОповещения("РасчетЗавершен", ЭтотОбъект);
    ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, Оповещение, ПараметрыОжидания);
КонецПроцедуры

&НаКлиенте
Процедура РасчетЗавершен(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = Неопределено Тогда
        Возврат; // пользователь отменил
    КонецЕсли;
    Если Результат.Статус = "Выполнено" Тогда
        Данные = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);
        // ... вывести результат ...
    ИначеЕсли Результат.Статус = "Ошибка" Тогда
        ПоказатьПредупреждение(, Результат.КраткоеПредставлениеОшибки);
    КонецЕсли;
КонецПроцедуры

Здесь РассчитатьОстатки — экспортная функция. Её возвращаемое значение БСП сама положит во временное хранилище. Для процедуры есть пара ПараметрыВыполненияПроцедуры и ВыполнитьПроцедуру.

Прогресс внутри функции сообщают так:

ДлительныеОперации.СообщитьПрогресс(Процент, "Обработано " + Формат(Обработано, "ЧГ=0"));

Процент — число от 0 до 100. На клиенте он попадает в окно ожидания.

Перед использованием откройте общий модуль ДлительныеОперации в своей конфигурации и сверьте состав параметров: в старых версиях БСП был метод ВыполнитьВФоне с другой сигнатурой. Описание каждого метода — в комментарии над ним.

Какой способ фонового запуска выбрать

СпособКогда подходитОграничения
ФоновыеЗадания.Выполнить + опросСвоя конфигурация без БСП, нужна полная управляемостьОпрос, прогресс и ошибки пишете сами
БСП «Длительные операции»Типовая или любая конфигурация на БСПЗависит от версии БСП, сверяйте сигнатуры
Регламентное заданиеРабота по расписанию: загрузки, обмены, закрытие периодаНет пользователя, результат — только в данных и журнале
ОжидатьЗавершенияВыполнения на сервереПараллельная обработка порций внутри фонового заданияБлокирует вызывающий сеанс
ПодключитьОбработчикОжидания без заданияОтложить лёгкое действие на клиентеЭто не фон: при вызове сервера клиент всё равно ждёт

Правило простое: если операция дольше 5–10 секунд и её запускает пользователь — фоновое задание с опросом. Если её никто не запускает руками — регламентное задание.

Обработка ошибок и журнал регистрации в фоновых заданиях

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

Схема в процедуре задания:

Процедура ЗагрузитьКурсы() Экспорт

    Попытка
        // ... основная работа ...
    Исключение
        ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ЗаписьЖурналаРегистрации("ФоновыеРасчеты.ЗагрузкаКурсов",
            УровеньЖурналаРегистрации.Ошибка, , , ТекстОшибки);
        ВызватьИсключение;
    КонецПопытки;

КонецПроцедуры

Повторный ВызватьИсключение обязателен. Без него задание получит статус «Завершено», и опрос сочтёт работу успешной.

Где смотреть следы:

  • 📜 журнал регистрации — Администрирование → Обслуживание → Журнал регистрации в типовых или Администрирование → Журнал регистрации в конфигураторе. Отбор по своему имени события находит записи за секунды;
  • 🧾 список фоновых заданий — в БСП на форме «Регламентные и фоновые задания», вкладка с фоновыми заданиями показывает состояние и текст ошибки;
  • 🖧 консоль кластера — для клиент-серверной базы видно активные фоновые сеансы и что они ждут.

Чтобы отменить зависшее задание из кода, найдите его через ФоновыеЗадания.НайтиПоУникальномуИдентификатору и вызовите Отменить(). Платформа прервёт выполнение и откатит незавершённую транзакцию.

Завершать фоновый сеанс из консоли кластера — крайняя мера. Незакоммиченные изменения откатятся, а если задание пишет данные порциями в разных транзакциях, часть порций останется записанной. Делайте это, когда понимаете, на каком этапе задание.

Не пишите в фоновом задании проверку «если ошибка — молча продолжить»: такие задания годами «работают», а данные расходятся.

Частые вопросы

Можно ли запустить в фоне процедуру модуля формы или объекта?
Напрямую нет. Метод фонового задания — экспортный метод серверного неглобального общего модуля. Вынесите логику в общий модуль, а из формы передайте ссылку на объект.
Работают ли фоновые задания в файловой базе?
Да, платформа выполняет их и в файловом варианте. Но параллельность там ограничена, а регламентные задания идут только при открытом клиентском сеансе. Нагрузочные сценарии проверяйте на клиент-серверной базе.
Сколько фоновых заданий можно запустить одновременно?
Жёсткого лимита в коде нет, предел — ресурсы сервера и блокировки данных. На практике порций делают столько, сколько ядер выделено рабочим процессам, иначе задания начинают ждать друг друга.
Под каким пользователем работает фоновое задание?
Под тем же, кто его запустил, с его правами. Для регламентного задания пользователь задаётся в его свойствах. Укажите там отдельного служебного пользователя с нужными правами — так в журнале регистрации сразу видно, что действие сделал регламент, а не человек.
Как передать в задание табличную часть формы?
Преобразуйте её на сервере в таблицу значений или массив структур и передайте как параметр. Для больших объёмов положите данные во временное хранилище и передайте адрес.