Фоновое выполнение кода в 1С 8.3: задания, обработчики ожидания и Асинх
Содержание
Чтобы выполнить процедуру в фоне 1С без зависания интерфейса, вынесите её в экспортную процедуру серверного общего модуля и запустите через ФоновыеЗадания.Выполнить() (или через ДлительныеОперации.ВыполнитьВФоне() в типовых конфигурациях на БСП). Форма при этом остаётся живой, а результат клиент забирает из временного хранилища по обработчику ожидания.
Обработчик ожидания и Асинх/Ждать тоже убирают «подвисание», но иначе: они не переносят расчёт в отдельный поток, а только меняют момент запуска кода на клиенте. Ниже код всех вариантов для платформы 8.3 и разбор, какой когда брать.
Что значит «в фоне» в 1С и почему форма зависает
Клиентский код 1С выполняется в одном потоке. Пока идёт вызов сервера, пользователь видит песочные часы и не может нажать ни одной кнопки. Если обработка документов идёт три минуты, все три минуты окно «мёртвое».
Есть три механизма, которые часто называют «фоновыми»:
- ⏱ обработчик ожидания — откладывает запуск процедуры на клиенте на N секунд;
- 🔄 асинхронные методы и
Асинх/Ждать— не блокируют интерфейс модальными окнами и диалогами; - 🖥 фоновое задание — запускает серверную процедуру в отдельном сеансе, параллельно работе пользователя.
Настоящий фон, при котором тяжёлый расчёт идёт, а пользователь продолжает работать, даёт только третий вариант. Первые два помогают с интерфейсом, но тяжёлый серверный вызов они не ускорят.
Отложенный запуск через обработчик ожидания
Метод ПодключитьОбработчикОжидания() вызывает указанную процедуру через заданный интервал в секундах. Типичная задача — открыть форму сразу, а заполнить её данными чуть позже, когда окно уже на экране.
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("ЗаполнитьДанные", 0.1, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ЗаполнитьДанные()
ЗаполнитьДанныеНаСервере();
КонецПроцедуры
Правила, которые важно соблюдать:
- Процедура-обработчик — клиентская и без параметров. Если она в модуле формы, экспорт не нужен; если в глобальном общем модуле — нужен
Экспорт. - Третий параметр
Истинаозначает однократный вызов. Без него процедура будет вызываться каждые N секунд, пока вы не выполнитеОтключитьОбработчикОжидания("ИмяПроцедуры"). - Имя процедуры передаётся строкой. Опечатка проявится только во время выполнения.
Пока ЗаполнитьДанныеНаСервере() выполняется, форма всё равно заблокирована. Обработчик ожидания лишь даёт окну отрисоваться. Если сам расчёт долгий, переходите к фоновому заданию.
Второе применение обработчика ожидания — периодический опрос. Именно так клиент узнаёт, что фоновое задание закончилось (пример ниже).
Асинх и Ждать: диалоги без блокировки интерфейса
Начиная с платформы 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С и что с ними делать:
Задание.ИнформацияОбОшибке, выводите его через ПодробноеПредставлениеОшибки(). Та же ошибка записывается в журнал регистрации.ИмяМодуля.ИмяПроцедуры.ЭтотОбъект. Передавайте ссылки, примитивные типы, массивы и структуры.Чтобы фон не превратился в новую проблему производительности:
- 📌 Не запускайте задание на каждое открытие формы. Десять пользователей открыли список — десять одинаковых заданий грузят сервер.
- 📌 Делите большой объём на порции. Пять заданий по 20 000 документов на сервере с несколькими рабочими процессами закончат быстрее одного на 100 000, но только если порции не пишут в одни и те же регистры, иначе задания будут ждать друг друга на блокировках.
- 📌 Пишите в журнал регистрации начало и конец задания с числом обработанных объектов. Это первое, что понадобится при разборе жалобы «ничего не пересчиталось».
- 📌 Помните про права: задание работает от имени пользователя, который его запустил, с его ролями. Привилегированный режим включайте только там, где это действительно нужно.
Список активных и завершённых заданий в клиент-серверной базе видно в консоли администрирования кластера и в обработке «Фоновые и регламентные задания» в типовых конфигурациях на БСП.
Частые вопросы
Можно ли запустить фоновое задание с клиента?
ФоновыеЗадания.Выполнить() доступен только на сервере. С клиента вызывайте серверную процедуру формы, а уже она запускает задание.Чем фоновое задание отличается от регламентного?
Можно ли отменить запущенное задание?
ФоновыеЗадания.НайтиПоУникальномуИдентификатору() и вызовите Отменить(). В БСП то же самое делает кнопка «Отмена» в окне ожидания.