Как из клиентской формы 1С отправить долгую операцию в фон и дождаться результата
Содержание
Фоновое задание в 1С 8.3 нельзя запустить прямо на клиенте: метод ФоновыеЗадания.Выполнить() доступен только в серверном контексте. Схема всегда одна: клиентская команда вызывает серверную процедуру, та стартует задание и возвращает его идентификатор, а клиент через ПодключитьОбработчикОжидания() периодически спрашивает сервер о состоянии и забирает результат из временного хранилища.
Типичная боль выглядит так: пользователь нажал «Сформировать», форма замерла на две минуты, а через минуту он уже снимает программу через диспетчер задач. Ниже — рабочий алгоритм запуска фонового задания на клиенте с готовым кодом, вариант через БСП, ограничения платформы и способы отладки. Всё описанное относится к платформе в целом и работает в любой конфигурации на управляемых формах.
Где на самом деле выполняется код: клиент, сервер, фон
Управляемое приложение делит код на контексты. От этого зависит, что вообще можно сделать «на клиенте».
| Контекст | Директива | Что доступно | Чего нет |
|---|---|---|---|
| Клиент | &НаКлиенте | Форма, диалоги, обработчики ожидания | Запросы, объекты базы, фоновые задания |
| Сервер с контекстом формы | &НаСервере | Запросы, реквизиты формы | Интерактив, диалоги |
| Сервер без контекста | &НаСервереБезКонтекста | Запросы, быстрая передача параметров | Реквизиты формы |
| Фоновое задание | процедура серверного общего модуля | Запросы, запись объектов | Форма, диалоги, любые клиентские методы |
Клиентский код 1С выполняется в одном потоке. Пока работает процедура &НаКлиенте или ждёт ответа серверный вызов, интерфейс не реагирует. Поэтому «запустить что-то в фоне на клиенте» означает другое: отдать работу отдельному серверному сеансу и не держать клиента в ожидании.
Фоновое задание — это отдельный сеанс без интерфейса. Он работает под тем же пользователем, что и запустивший его сеанс, со своими параметрами сеанса и своими транзакциями. В консоли кластера такие сеансы видны с приложением «Фоновое задание».
В файловом варианте фоновое задание исполняется внутри процесса клиентского приложения, которое его запустило. Закрыли программу — задание прервалось. В клиент-серверном варианте задание выполняет рабочий процесс сервера, и оно доживёт до конца даже после закрытия формы.
Какой способ выбрать: фон, обработчик ожидания или Асинх
Под фразой «асинхронно на клиенте» разработчики имеют в виду три разных механизма. Перепутать их — частая причина того, что форма всё равно висит.
| Механизм | Где работает | Когда подходит | Ограничение |
|---|---|---|---|
ФоновыеЗадания.Выполнить() | Отдельный серверный сеанс | Долгие запросы, пересчёт, загрузка, формирование файлов | Нет доступа к форме, нужен опрос состояния |
ПодключитьОбработчикОжидания() | Клиент, тот же поток | Периодический опрос, отложенный старт, таймер | Сам код обработчика тоже блокирует интерфейс |
Асинх / Ждать | Клиент | Диалоги, вопросы, выбор файла без модальности | Параллельности не даёт, только ждёт ответа |
БСП ДлительныеОперации | Обёртка над фоновыми заданиями | Типовые и самописные конфигурации на БСП | Нужна библиотека в конфигурации |
Правило простое. Если операция идёт дольше 5–10 секунд и не требует диалога с пользователем посередине — в фоновое задание. Обработчик ожидания нужен для опроса и таймеров. Асинх и Ждать решают задачу немодальных диалогов, а не распараллеливания расчётов.
Пример, где Асинх уместен:
&НаКлиенте
Асинх Процедура ОчиститьТаблицу(Команда)
Ответ = Ждать ВопросАсинх("Очистить таблицу результатов?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
ТаблицаДанных.Очистить();
КонецЕсли;
КонецПроцедуры
Код после Ждать продолжится, когда пользователь ответит. Но сама очистка выполняется на клиенте синхронно, никакого фона здесь нет.
Пошаговый запуск фонового задания из формы
Разберём полный цикл на примере формы обработки, которая собирает данные за период. У формы есть реквизиты:
- 📅
НачалоПериода,КонецПериода— тип «Дата»; - 🆔
ИдентификаторЗадания— тип «УникальныйИдентификатор»; - 📦
АдресРезультата— тип «Строка»; - 📊
ТаблицаДанных— таблица значений с колонками, совпадающими с результатом запроса.
Шаг 1. Серверный общий модуль с экспортной процедурой
Фоновое задание вызывает только процедуру общего модуля. Создайте общий модуль, например ОтчетыСервер, и установите у него флажок «Сервер». Флажок «Глобальный» не ставьте: методы глобального модуля в фоне запустить нельзя.
// Общий модуль ОтчетыСервер (Сервер = Истина)
Процедура СобратьДанные(НачалоПериода, КонецПериода, АдресРезультата) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &НачалоПериода И &КонецПериода
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Результат = Запрос.Выполнить().Выгрузить();
ПоместитьВоВременноеХранилище(Результат, АдресРезультата);
КонецПроцедуры
Имя регистра и поля замените на свои. Главное — процедура экспортная и кладёт результат по заранее переданному адресу.
Шаг 2. Серверная процедура формы, которая стартует задание
&НаСервере
Процедура ЗапуститьЗаданиеНаСервере()
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор);
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(НачалоПериода);
ПараметрыЗадания.Добавить(КонецПериода);
ПараметрыЗадания.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить("ОтчетыСервер.СобратьДанные",
ПараметрыЗадания, , "Сбор данных по продажам");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
КонецПроцедуры
Адрес хранилища создаётся заранее с уникальным идентификатором формы. Так результат проживёт, пока форма открыта, и удалится вместе с ней.
Третий параметр Выполнить() — ключ задания. Если передать, например, "СборПродаж", платформа не даст запустить второе задание с тем же ключом, пока первое активно, и выбросит исключение. Это удобная защита от двойного нажатия кнопки.
Шаг 3. Клиентская команда и обработчик ожидания
&НаКлиенте
Процедура Сформировать(Команда)
ЗапуститьЗаданиеНаСервере();
Элементы.Сформировать.Доступность = Ложь;
ПодключитьОбработчикОжидания("Подключаемый_ПроверитьЗадание", 1, Истина);
КонецПроцедуры
Третий параметр Истина делает обработчик однократным. Каждый раз мы переподключаем его заново — так проще управлять интервалом и не плодить повторные вызовы.
Опрос состояния и таймеры: как не перегрузить сервер
Обработчик ожидания — это и есть таймер в 1С. Интервал задаётся в секундах, дробные значения допустимы. Процедура должна быть в модуле формы с директивой &НаКлиенте или в клиентском глобальном модуле.
Проверка состояния задания
&НаКлиенте
Процедура Подключаемый_ПроверитьЗадание()
Состояние = СостояниеЗадания(ИдентификаторЗадания);
Если Состояние = "Выполняется" Тогда
ПодключитьОбработчикОжидания("Подключаемый_ПроверитьЗадание", 2, Истина);
ИначеЕсли Состояние = "Завершено" Тогда
ЗагрузитьРезультат();
Элементы.Сформировать.Доступность = Истина;
Иначе
Элементы.Сформировать.Доступность = Истина;
ПоказатьПредупреждение(, Состояние);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СостояниеЗадания(Идентификатор)
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Идентификатор);
Если Задание = Неопределено Тогда
Возврат "Задание не найдено";
КонецЕсли;
Для Каждого СообщениеЗадания Из Задание.ПолучитьСообщенияПользователю(Истина) Цикл
СообщениеЗадания.Сообщить();
КонецЦикла;
Если Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
Возврат "Выполняется";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Возврат "Завершено";
ИначеЕсли Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно Тогда
Возврат "Ошибка: " + ПодробноеПредставлениеОшибки(Задание.ИнформацияОбОшибке);
Иначе
Возврат "Задание отменено";
КонецЕсли;
КонецФункции
Функция без контекста формы передаёт на сервер только идентификатор. Это важно: опрос раз в 2 секунды с полной передачей формы на сервер заметно грузит канал, особенно в веб-клиенте и через интернет .
Как выбрать интервал
- ⏱ Первая проверка — через 1 секунду: короткие задания завершатся сразу.
- 📈 Дальше увеличивайте интервал до 2–5 секунд. Для операций на десятки минут хватит 10–15 секунд.
- 🧮 Считайте нагрузку: 50 пользователей с опросом раз в секунду дают 50 серверных вызовов в секунду только на проверку.
Удобный приём — хранить текущий интервал в переменной модуля формы и умножать его на 1,5 при каждой проверке, ограничив сверху 10 секундами.
Периодические задачи без фонового задания
Если нужно, например, раз в 5 минут обновлять счётчик новых документов, отдельное фоновое задание не требуется. Подключите многократный обработчик:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
ПодключитьОбработчикОжидания("Подключаемый_ОбновитьСчетчик", 300);
КонецПроцедуры
Сама проверка должна быть лёгкой — один быстрый запрос. Отключают обработчик методом ОтключитьОбработчикОжидания("Подключаемый_ОбновитьСчетчик"). Для задач, которые должны работать без открытой программы, используют регламентные задания, а не таймер на клиенте.
Что нельзя делать внутри фонового задания
Фоновое задание не знает про форму и про клиента. Отсюда набор ограничений, о которые спотыкаются чаще всего:
- 🚫 Никакого интерактива:
Вопрос(),ОткрытьФорму(),Предупреждение()в фоне вызовут ошибку. - 🚫 Нет доступа к реквизитам формы и к
ЭтотОбъектформы. Всё нужное передавайте параметрами. - 📦 Параметры должны быть сериализуемыми: примитивы, ссылки, массивы, структуры, таблицы значений. Данные формы и объекты интерфейса не передаются.
- 🔢 Параметры передаются массивом по порядку. Число элементов массива должно совпадать с числом параметров процедуры.
- 🧩 Вызываемый метод — только экспортная процедура или функция неглобального серверного общего модуля, имя пишется как
"ИмяМодуля.ИмяМетода". - 🔒 Задание работает с правами пользователя, который его запустил. Если у пользователя нет прав на регистр, фон упадёт с ошибкой доступа.
- 🔁 Внутри задания действуют обычные блокировки и транзакции. Два задания, пишущие в одни и те же записи, будут ждать друг друга.
Не вызывайте на сервере ожидание завершения задания сразу после его запуска (метод ОжидатьЗавершенияВыполнения()). Серверный вызов будет висеть, пока задание не закончится, и клиент снова замрёт. Смысл фона пропадёт.
Ещё одна деталь: функция, вызванная в фоне, может вернуть значение, но забрать его с клиента напрямую нельзя. Поэтому результат кладут во временное хранилище — так, как в примере выше.
Как вернуть результат и показать прогресс
Забираем результат из хранилища
&НаСервере
Процедура ЗагрузитьРезультат()
Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
Если ТипЗнч(Результат) = Тип("ТаблицаЗначений") Тогда
ЗначениеВРеквизитФормы(Результат, "ТаблицаДанных");
КонецЕсли;
КонецПроцедуры
Проверка типа защищает от ситуации, когда задание завершилось, но ничего не положило в хранилище, — там останется Неопределено.
Сообщения пользователю
Внутри задания пишите сообщения обычным способом:
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Обработано 500 документов из 2000";
Сообщение.Сообщить();
Сообщения не дойдут до клиента сами. Их накапливает задание, а функция СостояниеЗадания выше забирает их через ПолучитьСообщенияПользователю(Истина) и выводит на клиенте при каждом опросе. Параметр Истина удаляет полученные сообщения, чтобы они не повторялись.
Индикатор на форме
Самый простой прогресс без библиотек — передать в задание ещё один адрес хранилища и периодически класть туда процент. Но запись во временное хранилище из фона на каждом шаге цикла дорогая. Делайте её раз в 100–500 итераций.
В конфигурациях на БСП прогресс уже реализован: в фоне вызывают ДлительныеОперации.СообщитьПрогресс(), а клиентская часть сама показывает индикатор.
Тот же сценарий через БСП
Если в конфигурации есть Библиотека стандартных подсистем (а она есть во всех современных типовых), опрос писать не нужно. Серверная часть:
&НаСервере
Функция ЗапуститьНаСервере()
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = "Сбор данных по продажам";
ПараметрыПроцедуры = Новый Структура("НачалоПериода, КонецПериода",
НачалоПериода, КонецПериода);
Возврат ДлительныеОперации.ВыполнитьВФоне("ОтчетыСервер.СобратьДанныеБСП",
ПараметрыПроцедуры, ПараметрыВыполнения);
КонецФункции
Процедура для этого варианта принимает два параметра — структуру параметров и адрес результата: Процедура СобратьДанныеБСП(Параметры, АдресРезультата) Экспорт.
Клиентская часть:
&НаКлиенте
Процедура Сформировать(Команда)
ДлительнаяОперация = ЗапуститьНаСервере();
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ПараметрыОжидания.ВыводитьОкноОжидания = Истина;
Оповещение = Новый ОписаниеОповещения("ПриЗавершенииСбора", ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, Оповещение, ПараметрыОжидания);
КонецПроцедуры
&НаКлиенте
Процедура ПриЗавершенииСбора(Результат, ДополнительныеПараметры) Экспорт
Если Результат = Неопределено Тогда
Возврат; // пользователь отменил
КонецЕсли;
Если Результат.Статус = "Ошибка" Тогда
ПоказатьПредупреждение(, Результат.КраткоеПредставлениеОшибки);
Возврат;
КонецЕсли;
ЗагрузитьРезультатБСП(Результат.АдресРезультата);
КонецПроцедуры
БСП сама увеличивает интервал опроса, показывает окно ожидания с кнопкой отмены и передаёт сообщения из фона. Состав полей структуры результата может немного отличаться между версиями библиотеки — сверьте его с описанием функции ОжидатьЗавершение в общем модуле ДлительныеОперацииКлиент вашей конфигурации.
Отмена задания
Без БСП отмену делают отдельной кнопкой: серверная процедура находит задание по идентификатору и вызывает Задание.Отменить(). После этого состояние станет «Отменено», и опрос завершится. Учтите: отмена не откатывает уже записанные вне транзакции данные.
Отладка фоновых заданий и поиск узких мест
Точка останова в процедуре общего модуля по умолчанию не срабатывает: отладчик подключён к сеансу клиента, а задание идёт в другом сеансе.
Как подключить отладчик к фону
- Откройте Конфигуратор и запустите отладку как обычно.
- В меню
Отладка → Подключениеоткройте настройку автоматического подключения. - Включите флажок для фоновых заданий и закройте окно.
- Поставьте точку останова в процедуре общего модуля и запустите операцию из формы.
В клиент-серверном варианте сервер должен быть запущен с разрешённой отладкой. Если включить её нельзя (рабочий сервер), отлаживайте на копии базы.
Быстрый приём на время разработки: вызовите процедуру общего модуля напрямую из &НаСервере, без фона. Логику так отлаживать проще, а в фон переключать уже рабочий код. В БСП для этого в параметрах выполнения есть свойство ЗапуститьНеВФоне.
Где искать причины падений и медленной работы
- 📋 Журнал регистрации: отбор по приложению «Фоновое задание» и по времени запуска. Там видны ошибки, которые не дошли до формы.
- 🖥 Консоль кластера: список сеансов покажет, висит ли задание и сколько оно занимает. Перед тем как завершать такой сеанс, убедитесь, что это именно ваше задание: принудительное завершение прервёт операцию на середине.
- ⏱ Замер производительности в Конфигураторе работает и для фоновых заданий, если отладчик к ним подключён. Он покажет строку, на которую уходит основное время.
- 🔒 Ожидания на блокировках: если задание то идёт быстро, то стоит минутами, скорее всего, оно ждёт записи, которые держит другой сеанс или другое задание.