Разработчики, особенно начинающие переходить с обычных форм на управляемые, часто сталкиваются с терминами «асинхронность» и «параллельность». На первый взгляд они кажутся синонимами, ведь оба механизма призваны решать задачи повышения отзывчивости интерфейса и ускорения вычислений. Однако в архитектуре платформы «1С:Предприятие» эти понятия разведены по разным уровням системы и преследуют абсолютно разные технические цели.
В этой статье мы подробно разберем теоретическую базу этих понятий, выясним особенности их реализации в 1С, проанализируем эволюцию асинхронного программирования от модальных окон до современных «обещаний» (Promises) и рассмотрим, как запустить по-настоящему параллельные вычисления с помощью фоновых заданий.
Прежде чем перейти к коду 1С, давайте разберем эти понятия с точки зрения компьютерных наук:
Простыми словами: параллельность решает задачу «как делать несколько дел одновременно, чтобы закончить быстрее», а асинхронность — «как не стоять без дела и реагировать на внешние раздражители, пока выполняется долгая операция».
Клиентское приложение 1С (особенно веб-клиент, работающий в браузере) функционирует в строго однопоточном режиме. Это архитектурное ограничение, унаследованное от природы JavaScript в веб-браузерах. В один момент времени клиентское приложение выполняет ровно одну строчку кода встроенного языка.
Зачем же тогда на клиенте нужна асинхронность? Исключительно для того, чтобы интерфейс программы оставался отзывчивым (UI non-blocking). Если бы мы выполняли длительную операцию (например, чтение файла с диска или ожидание ответа пользователя в диалоговом окне) синхронно, интерфейс программы бы «зависал», а браузер выдавал бы предупреждение о неотвечающем скрипте.
Давайте проанализируем, как развивалась асинхронность в платформе «1С:Предприятие».
Платформа прошла три глобальных этапа развития работы с неблокирующими вызовами.
Этап 1. Синхронный режим (Модальность)
В старых версиях платформы (и в обычных формах) широко использовались модальные вызовы. Код останавливал свое выполнение в точке вызова и ждал действий пользователя:
// Устаревший синхронный код
Если Вопрос("Закрыть форму?", РежимДиалогаВопрос.ДаНет) = КодВозвратаДиалога.Да Тогда
Закрыть();
КонецЕсли;
Этот подход прост для написания, но он полностью блокирует поток выполнения и неприменим в современных веб-клиентах, так как браузеры блокируют модальные окна из соображений безопасности.
Этап 2. Описание оповещения (Callback-функции)
Начиная с версии 8.3.3, 1С начала отказываться от модальности. На смену пришли неблокирующие методы и объект ОписаниеОповещения. Этот подход заставлял разработчиков буквально разрывать один линейный алгоритм на несколько отдельных процедур:
&НаКлиенте
Процедура ПроверитьПередЗакрытием(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеВопроса", ЭтотОбъект);
ПоказатьВопрос(Оповещение, "Закрыть форму?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры
&НаКлиенте
Процедура ПослеВопроса(Результат, ДополнительныеПараметры) Экспорт
Если Результат = КодВозвратаДиалога.Да Тогда
Закрыть();
КонецЕсли;
КонецПроцедуры
Такой код сложно читать и отлаживать. Возникает так называемый «ад обратных вызовов» (callback hell), когда для реализации цепочки из трех диалогов нужно написать четыре отдельные процедуры.
Этап 3. Современная асинхронность через Обещания (Платформа 8.3.18+)
В современных версиях платформы появились новые ключевые слова Асинх, Ждать и новый тип данных Обещание (аналог Promise в веб-разработке). Это позволило писать асинхронный код так же просто, как и синхронный:
&НаКлиенте
Асинх Процедура ПроверитьПередЗакрытиемАсинх(Команда)
Результат = Ждать ПоказатьВопросАсинх("Закрыть форму?", РежимДиалогаВопрос.ДаНет);
Если Результат = КодВозвратаДиалога.Да Тогда
Закрыть();
КонецЕсли;
КонецПроцедуры
Давайте разберем по шагам, что происходит «под капотом» при выполнении этого кода:
Асинх. Это значит, что она возвращает управление вызывающему коду сразу же, как только встретит оператор Ждать, возвращая специальный объект Обещание.Ждать ПоказатьВопросАсинх(...), текущее состояние выполнения функции «замораживается». Стек вызовов не уничтожается, но поток выполнения освобождается.Результат.Важно помнить, что объект Обещание не сериализуется, поэтому его невозможно передать с клиента на сервер.
В отличие от однопоточного клиента, серверная часть 1С (работающая под управлением процессов rphost) спроектирована как мощная многопоточная система. Именно здесь реализуется настоящая, физическая параллельность.
Единственным штатным инструментом параллельного выполнения алгоритмов встроенного языка в 1С являются **фоновые задания**. Мы можем запустить несколько фоновых заданий, и они будут выполняться одновременно на разных ядрах процессора сервера приложений.
Давайте рассмотрим пример, когда нам нужно параллельно рассчитать тяжелые данные (например, себестоимость по разным филиалам) и дождаться результатов выполнения всех потоков.
&НаСервере
Функция ВыполнитьПараллельныеРасчеты() Экспорт
МассивЗаданий = Новый Массив;
УникальныйИдентификаторХранилища = Новый УникальныйИдентификатор;
// Подготавливаем адреса во временном хранилище для получения результатов
АдресРезультата1 = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификаторХранилища);
АдресРезультата2 = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификаторХранилища);
// Параметры для первого фонового задания
Параметры1 = Новый Массив;
Параметры1.Добавить("ФилиалСевер");
Параметры1.Добавить(АдресРезультата1);
// Параметры для второго фонового задания
Параметры2 = Новый Массив;
Параметры2.Добавить("ФилиалЮг");
Параметры2.Добавить(АдресРезультата2);
// Физически запускаем параллельное выполнение двух алгоритмов на сервере
Задание1 = ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьСебестоимостьПоФилиалу", Параметры1);
МассивЗаданий.Добавить(Задание1);
Задание2 = ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьСебестоимостьПоФилиалу", Параметры2);
МассивЗаданий.Добавить(Задание2);
// Ожидаем завершения работы обоих параллельных процессов
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
Исключение
ЗаписатьЛогОшибок("Одно из фоновых заданий аварийно завершилось.");
Возврат Неопределено;
КонецПопытки;
// Собираем результаты из временного хранилища
РезультатСевер = ПолучитьИзВременногоХранилища(АдресРезультата1);
РезультатЮг = ПолучитьИзВременногоХранилища(АдресРезультата2);
// Объединяем результаты расчетов
ОбщийРезультат = ОбъединитьРезультаты(РезультатСевер, РезультатЮг);
Возврат ОбщийРезультат;
КонецФункции
В данном примере метод ФоновыеЗадания.Выполнить() мгновенно возвращает управление, запуская выполнение метода общего модуля в отдельном рабочем потоке операционной системы на сервере. С помощью метода ФоновыеЗадания.ОжидатьЗавершения() мы временно приостанавливаем текущий сеанс до тех пор, пока все параллельные задания не закончат работу и не запишут результаты в глобальное для сервера Временное хранилище.
Обратите внимание на особенности файлового режима: в старых версиях платформы фоновые задания в файловой базе выполнялись строго последовательно. В современных версиях для файлового режима также создаются отдельные потоки, однако их параллельность ограничена пропускной способностью файловой СУБД.
Рассмотрим классический пример, упомянутый в дискуссии на форуме: когда пользователь формирует тяжелый отчет на СКД в управляемом интерфейсе, он может спокойно продолжать открывать другие документы и работать со списками. Создается полная иллюзия того, что клиентский код выполняется параллельно.
Проанализируем, как это устроено с точки зрения взаимодействия механизмов асинхронности и параллельности:
Таким образом, комфортная работа пользователя достигается за счет синергии двух технологий: **асинхронности** на стороне однопоточного клиента (чтобы не блокировать UI) и **параллельности** на стороне сервера (чтобы тяжелые расчеты выполнялись в отдельном независимом рабочем процессе).
Чтобы закрепить материал, посмотрим на ключевые различия этих подходов:
rphost), работающих параллельно на разных ядрах CPU.Асинх, оператор Ждать, объект ОписаниеОповещения, объект Обещание.ФоновыеЗадания, методы ФоновыеЗадания.Выполнить() и ФоновыеЗадания.ОжидатьЗавершения().Понимание этих базовых принципов работы платформы «1С:Предприятие» убережет вас от архитектурных ошибок при проектировании высоконагруженных систем и позволит создавать быстрые и отзывчивые интерфейсы для конечных пользователей.