В чем разница между параллельностью и асинхронностью в 1С и как они устроены?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработчики, особенно начинающие переходить с обычных форм на управляемые, часто сталкиваются с терминами «асинхронность» и «параллельность». На первый взгляд они кажутся синонимами, ведь оба механизма призваны решать задачи повышения отзывчивости интерфейса и ускорения вычислений. Однако в архитектуре платформы «1С:Предприятие» эти понятия разведены по разным уровням системы и преследуют абсолютно разные технические цели.

В этой статье мы подробно разберем теоретическую базу этих понятий, выясним особенности их реализации в 1С, проанализируем эволюцию асинхронного программирования от модальных окон до современных «обещаний» (Promises) и рассмотрим, как запустить по-настоящему параллельные вычисления с помощью фоновых заданий.

Теоретическое различие: параллельность против асинхронности

Прежде чем перейти к коду 1С, давайте разберем эти понятия с точки зрения компьютерных наук:

  1. Параллельность (Parallelism) — это физически одновременное выполнение нескольких операций. Она требует наличия многоядерного процессора или нескольких физических процессоров. В этом режиме разные потоки операционной системы одновременно выполняют разные участки кода на разных ядрах CPU.
  2. Асинхронность (Asynchrony) — это способ организации выполнения кода, при котором задача может быть запущена в фоновом режиме, позволяя основному потоку не блокироваться и продолжать обработку других событий. При этом физически код может выполняться последовательно на одном и том же ядре процессора (в рамках одного потока), просто контекст выполнения временно переключается.

Простыми словами: параллельность решает задачу «как делать несколько дел одновременно, чтобы закончить быстрее», а асинхронность — «как не стоять без дела и реагировать на внешние раздражители, пока выполняется долгая операция».

Асинхронность на клиенте в 1С

Клиентское приложение 1С (особенно веб-клиент, работающий в браузере) функционирует в строго однопоточном режиме. Это архитектурное ограничение, унаследованное от природы JavaScript в веб-браузерах. В один момент времени клиентское приложение выполняет ровно одну строчку кода встроенного языка.

Зачем же тогда на клиенте нужна асинхронность? Исключительно для того, чтобы интерфейс программы оставался отзывчивым (UI non-blocking). Если бы мы выполняли длительную операцию (например, чтение файла с диска или ожидание ответа пользователя в диалоговом окне) синхронно, интерфейс программы бы «зависал», а браузер выдавал бы предупреждение о неотвечающем скрипте.

Давайте проанализируем, как развивалась асинхронность в платформе «1С:Предприятие».

Эволюция асинхронного кода в 1С

Платформа прошла три глобальных этапа развития работы с неблокирующими вызовами.

Этап 1. Синхронный режим (Модальность)

В старых версиях платформы (и в обычных формах) широко использовались модальные вызовы. Код останавливал свое выполнение в точке вызова и ждал действий пользователя:


// Устаревший синхронный код
Если Вопрос("Закрыть форму?", РежимДиалогаВопрос.ДаНет) = КодВозвратаДиалога.Да Тогда
    Закрыть();
КонецЕсли;

Этот подход прост для написания, но он полностью блокирует поток выполнения и неприменим в современных веб-клиентах, так как браузеры блокируют модальные окна из соображений безопасности.

Этап 2. Описание оповещения (Callback-функции)

Начиная с версии 8.3.3, 1С начала отказываться от модальности. На смену пришли неблокирующие методы и объект ОписаниеОповещения. Этот подход заставлял разработчиков буквально разрывать один линейный алгоритм на несколько отдельных процедур:


&НаКлиенте
Процедура ПроверитьПередЗакрытием(Команда)
    Оповещение = Новый ОписаниеОповещения("ПослеВопроса", ЭтотОбъект);
    ПоказатьВопрос(Оповещение, "Закрыть форму?", РежимДиалогаВопрос.ДаНет);
КонецПроцедуры

&НаКлиенте
Процедура ПослеВопроса(Результат, ДополнительныеПараметры) Экспорт
    Если Результат = КодВозвратаДиалога.Да Тогда
        Закрыть();
    КонецЕсли;
КонецПроцедуры

Такой код сложно читать и отлаживать. Возникает так называемый «ад обратных вызовов» (callback hell), когда для реализации цепочки из трех диалогов нужно написать четыре отдельные процедуры.

Этап 3. Современная асинхронность через Обещания (Платформа 8.3.18+)

В современных версиях платформы появились новые ключевые слова Асинх, Ждать и новый тип данных Обещание (аналог Promise в веб-разработке). Это позволило писать асинхронный код так же просто, как и синхронный:


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

Давайте разберем по шагам, что происходит «под капотом» при выполнении этого кода:

  1. Процедура помечена модификатором Асинх. Это значит, что она возвращает управление вызывающему коду сразу же, как только встретит оператор Ждать, возвращая специальный объект Обещание.
  2. Когда выполнение доходит до Ждать ПоказатьВопросАсинх(...), текущее состояние выполнения функции «замораживается». Стек вызовов не уничтожается, но поток выполнения освобождается.
  3. Пока пользователь думает и не нажал на кнопку в диалоге, клиентское приложение 1С остается полностью работоспособным. Пользователь может открывать другие формы, двигать окна или запускать другие отчеты.
  4. Как только пользователь делает выбор, платформа возобновляет выполнение нашей процедуры ровно с той строки, где оно было приостановлено, помещая результат выбора в переменную Результат.

Важно помнить, что объект Обещание не сериализуется, поэтому его невозможно передать с клиента на сервер.

Параллельность на сервере в 1С: Фоновые задания

В отличие от однопоточного клиента, серверная часть 1С (работающая под управлением процессов rphost) спроектирована как мощная многопоточная система. Именно здесь реализуется настоящая, физическая параллельность.

Единственным штатным инструментом параллельного выполнения алгоритмов встроенного языка в 1С являются **фоновые задания**. Мы можем запустить несколько фоновых заданий, и они будут выполняться одновременно на разных ядрах процессора сервера приложений.

Давайте рассмотрим пример, когда нам нужно параллельно рассчитать тяжелые данные (например, себестоимость по разным филиалам) и дождаться результатов выполнения всех потоков.


&НаСервере
Функция ВыполнитьПараллельныеРасчеты() Экспорт
    
    МассивЗаданий = Новый Массив;
    УникальныйИдентификаторХранилища = Новый УникальныйИдентификатор;
    
    // Подготавливаем адреса во временном хранилище для получения результатов
    АдресРезультата1 = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификаторХранилища);
    АдресРезультата2 = ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификаторХранилища);
    
    // Параметры для первого фонового задания
    Параметры1 = Новый Массив;
    Параметры1.Добавить("ФилиалСевер");
    Параметры1.Добавить(АдресРезультата1);
    
    // Параметры для второго фонового задания
    Параметры2 = Новый Массив;
    Параметры2.Добавить("ФилиалЮг");
    Параметры2.Добавить(АдресРезультата2);
    
    // Физически запускаем параллельное выполнение двух алгоритмов на сервере
    Задание1 = ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьСебестоимостьПоФилиалу", Параметры1);
    МассивЗаданий.Добавить(Задание1);
    
    Задание2 = ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьСебестоимостьПоФилиалу", Параметры2);
    МассивЗаданий.Добавить(Задание2);
    
    // Ожидаем завершения работы обоих параллельных процессов
    Попытка
        ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий);
    Исключение
        ЗаписатьЛогОшибок("Одно из фоновых заданий аварийно завершилось.");
        Возврат Неопределено;
    КонецПопытки;
    
    // Собираем результаты из временного хранилища
    РезультатСевер = ПолучитьИзВременногоХранилища(АдресРезультата1);
    РезультатЮг = ПолучитьИзВременногоХранилища(АдресРезультата2);
    
    // Объединяем результаты расчетов
    ОбщийРезультат = ОбъединитьРезультаты(РезультатСевер, РезультатЮг);
    
    Возврат ОбщийРезультат;
    
КонецФункции

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

Обратите внимание на особенности файлового режима: в старых версиях платформы фоновые задания в файловой базе выполнялись строго последовательно. В современных версиях для файлового режима также создаются отдельные потоки, однако их параллельность ограничена пропускной способностью файловой СУБД.

Иллюзия параллельности: кейс формирования отчетов СКД

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

Проанализируем, как это устроено с точки зрения взаимодействия механизмов асинхронности и параллельности:

  1. Пользователь нажимает кнопку «Сформировать». На клиенте срабатывает обработчик.
  2. Платформа не начинает компоновать результат непосредственно в клиентском потоке. Вместо этого она неявно отправляет запрос на сервер и запускает там **фоновое задание** (параллельный поток на сервере) для выполнения вычислений СКД и построения макета.
  3. Клиентское приложение переходит в режим **асинхронного ожидания**. На клиенте запускается фоновый системный таймер, который с определенной периодичностью опрашивает сервер: «Готов ли отчет?».
  4. Поскольку основной поток выполнения кода на клиенте свободен в промежутках между тиками этого таймера, интерфейс остается отзывчивым. Пользователь может открывать новые формы, вводить данные и проводить документы.
  5. Как только сервер сигнализирует о готовности отчета, асинхронный обработчик на клиенте получает готовый табличный документ и выводит его на экран пользователя.

Таким образом, комфортная работа пользователя достигается за счет синергии двух технологий: **асинхронности** на стороне однопоточного клиента (чтобы не блокировать UI) и **параллельности** на стороне сервера (чтобы тяжелые расчеты выполнялись в отдельном независимом рабочем процессе).

Сводная таблица сравнения механизмов

Чтобы закрепить материал, посмотрим на ключевые различия этих подходов:

  1. Где выполняется код:
    • Асинхронность: Преимущественно на клиенте (взаимодействие с файлами, внешним оборудованием, веб-сервисами, интерфейсом).
    • Параллельность: Исключительно на сервере приложений (фоновые и регламентные задания).
  2. Количество потоков ОС:
    • Асинхронность: Один поток на клиенте. Выполнение прерывается и возобновляется в рамках единого контекста выполнения.
    • Параллельность: Несколько независимых потоков ОС (процессы rphost), работающих параллельно на разных ядрах CPU.
  3. Инструменты встроенного языка:
    • Асинхронность: Модификатор Асинх, оператор Ждать, объект ОписаниеОповещения, объект Обещание.
    • Параллельность: Менеджер ФоновыеЗадания, методы ФоновыеЗадания.Выполнить() и ФоновыеЗадания.ОжидатьЗавершения().
  4. Основная цель использования:
    • Асинхронность: Сохранение отзывчивости интерфейса, поддержка веб-стандартов браузеров, исключение зависаний.
    • Параллельность: Ускорение сложных математических расчетов, проведение тяжелых регламентных процедур, распределение нагрузки по ядрам процессора.

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

← На главную