Фоновое выполнение кода в 1С 8.3: задания, обработчики ожидания и Асинх

· 9 мин

Содержание

Чтобы выполнить процедуру в фоне 1С без зависания интерфейса, вынесите её в экспортную процедуру серверного общего модуля и запустите через ФоновыеЗадания.Выполнить() (или через ДлительныеОперации.ВыполнитьВФоне() в типовых конфигурациях на БСП). Форма при этом остаётся живой, а результат клиент забирает из временного хранилища по обработчику ожидания.

Обработчик ожидания и Асинх/Ждать тоже убирают «подвисание», но иначе: они не переносят расчёт в отдельный поток, а только меняют момент запуска кода на клиенте. Ниже код всех вариантов для платформы 8.3 и разбор, какой когда брать.

Что значит «в фоне» в 1С и почему форма зависает

Клиентский код 1С выполняется в одном потоке. Пока идёт вызов сервера, пользователь видит песочные часы и не может нажать ни одной кнопки. Если обработка документов идёт три минуты, все три минуты окно «мёртвое».

Есть три механизма, которые часто называют «фоновыми»:

  • ⏱ обработчик ожидания — откладывает запуск процедуры на клиенте на N секунд;
  • 🔄 асинхронные методы и Асинх/Ждать — не блокируют интерфейс модальными окнами и диалогами;
  • 🖥 фоновое задание — запускает серверную процедуру в отдельном сеансе, параллельно работе пользователя.

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

Отложенный запуск через обработчик ожидания

Метод ПодключитьОбработчикОжидания() вызывает указанную процедуру через заданный интервал в секундах. Типичная задача — открыть форму сразу, а заполнить её данными чуть позже, когда окно уже на экране.

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    ПодключитьОбработчикОжидания("ЗаполнитьДанные", 0.1, Истина);
КонецПроцедуры

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

Правила, которые важно соблюдать:

  1. Процедура-обработчик — клиентская и без параметров. Если она в модуле формы, экспорт не нужен; если в глобальном общем модуле — нужен Экспорт.
  2. Третий параметр Истина означает однократный вызов. Без него процедура будет вызываться каждые N секунд, пока вы не выполните ОтключитьОбработчикОжидания("ИмяПроцедуры").
  3. Имя процедуры передаётся строкой. Опечатка проявится только во время выполнения.

Пока ЗаполнитьДанныеНаСервере() выполняется, форма всё равно заблокирована. Обработчик ожидания лишь даёт окну отрисоваться. Если сам расчёт долгий, переходите к фоновому заданию.

Второе применение обработчика ожидания — периодический опрос. Именно так клиент узнаёт, что фоновое задание закончилось (пример ниже).

Асинх и Ждать: диалоги без блокировки интерфейса

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

&НаКлиенте
Асинх Процедура УдалитьОтмеченные(Команда)
    Ответ = Ждать ВопросАсинх("Удалить отмеченные строки?", РежимДиалогаВопрос.ДаНет);
    Если Ответ = КодВозвратаДиалога.Да Тогда
        УдалитьОтмеченныеНаСервере();
    КонецЕсли;
КонецПроцедуры

Что нужно знать про асинхронные вызовы:

  • 🔹 Ждать можно писать только внутри процедуры или функции, объявленной с Асинх;
  • 🔹 режим совместимости конфигурации должен быть не ниже 8.3.18, иначе слова не распознаются;
  • 🔹 асинхронными становятся клиентские диалоги и операции с файлами, а вызов &НаСервере по-прежнему синхронный.

Для старых конфигураций тот же эффект даёт пара ПоказатьВопрос() + Новый ОписаниеОповещения("ИмяПроцедуры", ЭтотОбъект). Процедура-обработчик ответа в модуле формы должна быть экспортной.

💡 Асинхронность решает проблему модальных окон, особенно в веб-клиенте. Для тяжёлого расчёта на сервере она бесполезна.

Фоновое задание: как выполнить процедуру в фоне на сервере

Фоновое задание — отдельный сеанс на сервере 1С, который выполняет экспортную процедуру общего модуля. Пользовательский сеанс в это время свободен.

Шаг 1. Подготовьте процедуру в общем модуле

Создайте общий модуль, например ФоновыеОперацииСервер. В его свойствах отметьте флажок «Сервер», флажок «Глобальный» оставьте снятым. Процедура должна быть экспортной.

Процедура ПересчитатьДокументы(ДатаНачала, АдресРезультата) Экспорт

    Запрос = Новый Запрос(
        "ВЫБРАТЬ
        |   Док.Ссылка КАК Ссылка
        |ИЗ
        |   Документ.РеализацияТоваровУслуг КАК Док
        |ГДЕ
        |   Док.Дата >= &ДатаНачала
        |   И Док.Проведен");
    Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);

    Выборка = Запрос.Выполнить().Выбрать();
    Обработано = 0;
    Пока Выборка.Следующий() Цикл
        // здесь ваша обработка документа
        Обработано = Обработано + 1;
    КонецЦикла;

    ПоместитьВоВременноеХранилище(Обработано, АдресРезультата);

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

В задании нет клиента. Формы, Вопрос(), ОткрытьФорму() и любые интерактивные методы там не работают.

Шаг 2. Запустите задание из формы

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

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

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

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

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

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

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

Параметры передаются массивом в том же порядке, что и в объявлении процедуры. Третий параметр — ключ задания, четвёртый — наименование, которое видно в списке заданий.

Адрес хранилища привязан к УникальныйИдентификатор формы. Значение проживёт, пока форма открыта, и клиент успеет его забрать.

Шаг 3. Проверяйте состояние по таймеру

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

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

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

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

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

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

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

    Возврат "Ошибка";

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

Обработчик подключается однократно и переподключает сам себя. Так проще остановить опрос: не переподключили — опрос закончился.

Опрос раз в 1–2 секунды достаточен. Если задание заведомо длится минутами, увеличивайте интервал: каждый опрос — это вызов сервера.

Шаг 4 (по желанию). Запустите несколько заданий параллельно

Если объём большой, его можно разбить на порции и запустить по заданию на каждую. Например, обработку за квартал — тремя заданиями, по месяцу на каждое.

&НаСервере
Процедура ЗапуститьПоМесяцам(НачалоКвартала)

    СписокЗаданий.Очистить();
    Для НомерМесяца = 0 По 2 Цикл
        НачалоПорции = ДобавитьМесяц(НачалоКвартала, НомерМесяца);
        Адрес = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);

        ПараметрыЗадания = Новый Массив;
        ПараметрыЗадания.Добавить(НачалоПорции);
        ПараметрыЗадания.Добавить(Адрес);

        Задание = ФоновыеЗадания.Выполнить("ФоновыеОперацииСервер.ПересчитатьДокументы",
            ПараметрыЗадания, , "Пересчёт с " + Формат(НачалоПорции, "ДФ=dd.MM.yyyy"));
        СписокЗаданий.Добавить(Задание.УникальныйИдентификатор, Адрес);
    КонецЦикла;

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

Здесь СписокЗаданий — реквизит формы типа СписокЗначений: в значении лежит идентификатор задания, в представлении — адрес результата. Клиент опрашивает все задания и считает работу законченной, когда активных не осталось.

Процедуру расчёта при этом нужно доработать: каждая порция должна ограничиваться концом своего месяца, иначе задания будут обрабатывать одни и те же документы. В файловой базе параллельность почти ничего не даёт, выигрыш заметен только на сервере 1С с несколькими рабочими процессами.

Запуск в фоне через БСП: модуль ДлительныеОперации

В типовых конфигурациях (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) есть готовая обёртка из Библиотеки стандартных подсистем. Она сама создаёт хранилище, показывает окно ожидания с кнопкой отмены и опрашивает задание. В доработках типовых пишите через неё, а не вручную.

&НаКлиенте
Процедура Пересчитать(Команда)

    ДлительнаяОперация = НачатьПересчет();

    ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
    ПараметрыОжидания.ТекстСообщения = "Пересчитываем документы...";
    ПараметрыОжидания.ВыводитьОкноОжидания = Истина;

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

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

&НаСервере
Функция НачатьПересчет()

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

    ПараметрыПроцедуры = Новый Структура("ДатаНачала", НачалоМесяца(ТекущаяДатаСеанса()));

    Возврат ДлительныеОперации.ВыполнитьВФоне("ФоновыеОперацииСервер.ПересчитатьДокументыБСП",
        ПараметрыПроцедуры, ПараметрыВыполнения);

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

&НаКлиенте
Процедура ПослеПересчета(Результат, ДополнительныеПараметры) Экспорт

    Если Результат = Неопределено Тогда
        Возврат; // пользователь нажал «Отмена»
    КонецЕсли;

    Если Результат.Статус = "Ошибка" Тогда
        ПоказатьПредупреждение(, Результат.КраткоеПредставлениеОшибки);
        Возврат;
    КонецЕсли;

    Обработано = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);
    ПоказатьОповещениеПользователя("Готово", , "Обработано: " + Формат(Обработано, "ЧГ=0"));

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

Серверная процедура для ВыполнитьВФоне принимает ровно два параметра: структуру параметров и адрес результата.

Процедура ПересчитатьДокументыБСП(Параметры, АдресРезультата) Экспорт
    Обработано = 0;
    // обработка с использованием Параметры.ДатаНачала
    ПоместитьВоВременноеХранилище(Обработано, АдресРезультата);
КонецПроцедуры

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

Прогресс выполнения задание сообщает вызовом ДлительныеОперации.СообщитьПрогресс(Процент, Текст). Чтобы окно ожидания его показывало, установите ПараметрыОжидания.ВыводитьПрогрессВыполнения = Истина.

Сравнение способов фонового выполнения

СпособГде выполняется кодБлокирует форму во время расчётаКогда брать
Обработчик ожиданияКлиент, тот же потокДаОтложить заполнение формы, периодический опрос
Асинх / ЖдатьКлиентНет, но только для диалогов и файловВопросы, выбор файлов, внешние компоненты
ФоновыеЗадания.Выполнить()Сервер, отдельный сеансНетДолгие расчёты в своей конфигурации или обработке
ДлительныеОперации.ВыполнитьВФоне()Сервер, отдельный сеансНетДоработки типовых на БСП

Короткое правило: если тормозит сервер — нужно фоновое задание. Если тормозит интерфейс из-за окон и диалогов — асинхронные методы.

Как вернуть результат и уведомить пользователя

Фоновое задание не может вернуть значение напрямую. Рабочие каналы такие:

  1. Временное хранилище. Задание пишет результат по переданному адресу, клиент читает его через ПолучитьИзВременногоХранилища(). Подходит для чисел, структур, таблиц значений, двоичных данных файла.
  2. Сообщения пользователю. Внутри задания создайте Новый СообщениеПользователю, заполните Текст и вызовите Сообщить(). На сервере сообщения забирают методом Задание.ПолучитьСообщенияПользователю(Истина) и выводят в форму. Параметр Истина удаляет полученные сообщения, чтобы при следующем опросе они не дублировались.
  3. Запись в базу. Если задание формирует документы, результат — сами документы. Клиенту достаточно обновить список через Оповестить("ИмяСобытия") или Элементы.Список.Обновить().

Для уведомления о завершении удобен ПоказатьОповещениеПользователя(): всплывающее окно в углу не мешает работе. ПоказатьПредупреждение() оставьте для ошибок, которые требуют реакции.

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

Ошибки и нагрузка при выполнении в фоне

Самые частые проблемы при фоновом выполнении процедуры в 1С и что с ними делать:

Диагностика: выберите симптом

Чтобы фон не превратился в новую проблему производительности:

  • 📌 Не запускайте задание на каждое открытие формы. Десять пользователей открыли список — десять одинаковых заданий грузят сервер.
  • 📌 Делите большой объём на порции. Пять заданий по 20 000 документов на сервере с несколькими рабочими процессами закончат быстрее одного на 100 000, но только если порции не пишут в одни и те же регистры, иначе задания будут ждать друг друга на блокировках.
  • 📌 Пишите в журнал регистрации начало и конец задания с числом обработанных объектов. Это первое, что понадобится при разборе жалобы «ничего не пересчиталось».
  • 📌 Помните про права: задание работает от имени пользователя, который его запустил, с его ролями. Привилегированный режим включайте только там, где это действительно нужно.

Список активных и завершённых заданий в клиент-серверной базе видно в консоли администрирования кластера и в обработке «Фоновые и регламентные задания» в типовых конфигурациях на БСП.

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

Можно ли запустить фоновое задание с клиента?
Нет. ФоновыеЗадания.Выполнить() доступен только на сервере. С клиента вызывайте серверную процедуру формы, а уже она запускает задание.
Чем фоновое задание отличается от регламентного?
Фоновое запускается из кода по требованию. Регламентное — объект конфигурации с расписанием: платформа сама создаёт для него фоновое задание в нужное время.
Можно ли отменить запущенное задание?
Да. Найдите его через ФоновыеЗадания.НайтиПоУникальномуИдентификатору() и вызовите Отменить(). В БСП то же самое делает кнопка «Отмена» в окне ожидания.
Работают ли фоновые задания в веб-клиенте и мобильном клиенте?
Задание выполняется на сервере, поэтому вид клиента не важен. Опрос состояния через обработчик ожидания работает и в веб-клиенте.
Почему внутри задания пустые параметры сеанса?
Задание — отдельный сеанс. Параметры сеанса в нём заполняются заново при первом обращении, значения из сеанса пользователя туда не переходят. Нужные данные передавайте параметрами процедуры.