В платформе 1С:Предприятие 8.3, начиная с версии 8.3.18, появился полноценный синтаксис для написания асинхронного кода с использованием ключевых слов Асинх (Async) и Ждать (Await). Это нововведение было призвано упростить написание кода, работающего с блокирующими интерфейс операциями (диалоговые окна) и серверными вызовами, избавив разработчиков от сложной вложенности описаний оповещений (так называемого "Callback Hell").
Однако, многие разработчики, пробуя применить этот механизм для создания собственных "фоновых" функций или пауз, сталкиваются с тем, что код работает не так, как ожидалось. Вместо параллельного выполнения программа "зависает" и выполняет код последовательно. Давайте подробно разберем, почему так происходит, как на самом деле работает асинхронность в 1С и какие существуют ограничения.
Рассмотрим типичную ситуацию, с которой сталкиваются программисты. Мы хотим создать функцию, которая имитирует длительную операцию (например, паузу или сложные вычисления), и запустить её так, чтобы она не блокировала выполнение остального кода. Мы ожидаем, что, пометив функцию как Асинх, мы заставим её выполняться "в фоне", пока основной поток идет дальше.
Вот пример кода, который часто пытаются написать разработчики, ожидая асинхронного поведения:
&НаКлиенте
Асинх Процедура Команда1(Команда)
// Мы ожидаем, что Пауза начнет выполняться, а мы пойдем дальше
Результат = Пауза(5);
// Показываем предупреждение СРАЗУ, пока идет пауза
Ждать ПредупреждениеАсинх("Сейчас будем ждать завершения паузы");
// И только здесь дожидаемся результата
Ответ = Ждать Результат;
Сообщить("Готово");
КонецПроцедуры
&НаКлиенте
Асинх Функция Пауза(Секунд)
ДатаВыхода = ТекущаяДата() + Секунд;
// Имитация бурной деятельности (активное ожидание)
Пока ТекущаяДата() < ДатаВыхода Цикл
// Пустой цикл, занимающий процессорное время
КонецЦикла;
Возврат Секунд;
КонецФункции
Ожидание: При нажатии кнопки запускается функция Пауза(5). Переменная Результат получает объект "Обещание" (Promise). Сразу же появляется окно предупреждения. И только после закрытия окна и истечения 5 секунд мы получаем "Готово".
Реальность: Интерфейс "зависает" на 5 секунд сразу на строке Результат = Пауза(5);. Никакое окно не появляется, пока цикл в функции Пауза не завершится. Код выполняется строго синхронно, строка за строкой.
Чтобы понять причину такого поведения, нам нужно четко разграничить два понятия: асинхронность и многопоточность.
Клиентское приложение 1С (Тонкий клиент, Веб-клиент) работает в одном основном потоке. Этот поток отвечает и за выполнение вашего кода на встроенном языке, и за перерисовку кнопок, и за реакцию на клики мыши. Если вы занимаете этот поток длительным циклом (как в примере выше), приложение перестает отвечать на что-либо другое.
Давайте проанализируем, как платформа обрабатывает функцию с модификатором Асинх.
Когда вы пишете Асинх Функция, компилятор 1С преобразует этот код в конечный автомат (State Machine). Ключевое слово Ждать (Await) является точкой разрыва. В этой точке выполнение функции приостанавливается, управление возвращается платформе, и платформа ждет, пока "Обещание" (Promise), стоящее справа от Ждать, не будет выполнено.
Однако, посмотрите внимательно на функцию Пауза из нашего примера:
Асинх Функция Пауза(Секунд)
ДатаВыхода = ТекущаяДата() + Секунд;
Пока ТекущаяДата() < ДатаВыхода Цикл
КонецЦикла;
Возврат Секунд;
КонецФункции
Внутри этой функции нет ни одного оператора Ждать. В ней содержится только синхронный код (цикл Пока). Платформа заходит в эту функцию и начинает выполнять инструкции по порядку. Поскольку нет команд "прерваться и подождать чего-то внешнего" (например, ответа пользователя), функция выполняется от начала до конца, полностью занимая процессорное время.
То, что функция помечена как Асинх, лишь означает, что она возвращает результат, завернутый в Обещание. Но если внутри функции нет реальных асинхронных вызовов платформы, это Обещание возвращается уже в статусе "Выполнено" (Resolved), и происходит это только после того, как весь код функции отработает.
Важный вывод: Вы не можете создать свою асинхронную функцию, просто написав Асинх. Чтобы функция стала действительно асинхронной (не блокирующей), она должна внутри себя вызывать другие платформенные асинхронные методы через Ждать.
В обсуждении часто приводят примеры из JavaScript. В JS похожий код с циклом while тоже подвесит браузер. Чтобы сделать паузу в JS, используют setTimeout, обернутый в Promise:
// Пример на JS (псевдокод)
await new Promise(resolve => setTimeout(resolve, 5000));
В 1С разработчики лишены возможности создавать свои экземпляры объекта Обещание вручную. Мы не можем написать Новый Обещание(...). Мы можем только получать обещания от системных методов. А системного метода "Подождать N секунд и вернуть обещание" (аналога Thread.Sleep, но асинхронного) в платформе на данный момент нет.
Поскольку мы выяснили, что Асинх/Ждать в 1С предназначены для управления потоком выполнения (чтобы избежать вложенных процедур обработки оповещений), а не для параллелизма, давайте рассмотрим правильные способы решения задачи.
Если ваша задача — выполнить сложные вычисления или длительную обработку данных, не блокируя интерфейс пользователя, единственным правильным решением является использование Фоновых заданий на сервере.
Алгоритм действий:
ПодключитьОбработчикОжидания, который раз в пару секунд спрашивает сервер: "Готово?".Пример (концептуальный):
&НаКлиенте
Процедура ЗапуститьДлительнуюОперацию()
Параметры = Новый Структура;
// Запускаем фоновое задание
РезультатЗапуска = ЗапуститьВыполнениеНаСервере(Параметры);
ИдентификаторЗадания = РезультатЗапуска.Идентификатор;
// Подключаем "таймер" для проверки статуса
ПодключитьОбработчикОжидания("ПроверитьСостояниеЗадания", 1, Ложь);
Сообщить("Задание запущено, вы можете продолжать работу");
КонецПроцедуры
Если вам нужно просто сделать паузу в интерфейсе (например, показать сообщение и закрыть его через 5 секунд), использование цикла Пока ТекущаяДата() < ... является грубой ошибкой (оно называется "Active Waiting" или "Busy Wait"). Процессор загружается на 100%, интерфейс виснет.
Правильный подход — использование метода ПодключитьОбработчикОжидания. Он разбивает выполнение кода на две процедуры: "до паузы" и "после паузы".
&НаКлиенте
Процедура Команда1(Команда)
Сообщить("Начало");
// Планируем выполнение второй части через 5 секунд
ПодключитьОбработчикОжидания("ПродолжениеВыполнения", 5, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПродолжениеВыполнения()
Сообщить("Прошло 5 секунд");
КонецПроцедуры
К сожалению, этот метод нельзя красиво "встроить" в линейный код с помощью Ждать. Это архитектурное ограничение платформы.
Давайте подведем итоги анализа темы:
Асинх в 1С не создает новый поток. Весь код выполняется в одном потоке последовательно.Ждать, который вызывает реальный асинхронный метод платформы (например, вопрос пользователю или асинхронный вызов сервера).Пока...Цикл внутри асинхронных функций блокируют интерфейс так же, как и в обычных процедурах.Обещание является системным, создать его вручную для реализации своих паттернов ожидания нельзя.Используйте механизм Асинх/Ждать по его прямому назначению: для упрощения кода при работе с диалоговыми окнами (ВопросАсинх, ПредупреждениеАсинх, ВыбратьИзМенюАсинх) и неблокирующими вызовами. Для параллельных вычислений используйте фоновые задания на сервере.