Как реализовать асинхронное выполнение произвольных функций в 1С с использованием Асинх/Ждать?

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

Многие разработчики 1С, знакомясь с новым синтаксисом Асинх/Ждать, сталкиваются с ожиданием, что он позволит легко и красиво организовать асинхронное выполнение любых пользовательских функций, особенно длительных серверных операций, с возможностью ожидания результата на клиенте в неблокирующем режиме. Однако на практике возможности этого синтаксиса несколько отличаются от первоначальных представлений. Давайте рассмотрим, почему это так, и какие механизмы следует использовать для решения подобных задач в текущей версии платформы 1С.

Основные ожидания и разочарования

Итак, в платформе 1С появился синтаксис Асинх/Ждать, призванный упростить работу с асинхронностью, сделать код более читаемым, избавляя от "лапши" из колбэков и обработчиков оповещений. Естественным желанием многих разработчиков было использовать его для следующего сценария:

Результат = Ждать ВыполнитьФоново(ПутьФункции, ПараметрыФункции);

где ВыполнитьФоново была бы произвольной, созданной нами функцией, запускающей длительную операцию на сервере, а Ждать позволял бы клиенту не блокироваться, но при этом "дождаться" результата в той же точке кода, где был сделан вызов. К сожалению, напрямую в таком виде эта конструкция не работает для произвольных пользовательских функций.

Различие асинхронности, многопоточности и фоновых заданий

Прежде всего, давайте внесем ясность в терминологию. Важно понимать, что асинхронность и многопоточность — это разные понятия. В 1С:

  1. Асинхронность (Асинх/Ждать) в первую очередь направлена на то, чтобы не блокировать основной поток выполнения на клиенте (UI-поток) во время длительных операций, которые происходят на самом клиенте (например, работа с файлами, диалоги, криптография). Это позволяет пользовательскому интерфейсу оставаться отзывчивым, пока операция выполняется в фоновом режиме на той же клиентской машине. Асинхронная функция всегда возвращает объект типа Обещание, который является оберткой для будущего результата. Оператор Ждать приостанавливает выполнение текущей асинхронной функции до тех пор, пока Обещание не будет разрешено (успешно завершено или отклонено с ошибкой).
  2. Многопоточность – это выполнение нескольких потоков команд одновременно внутри одного процесса. В 1С в рамках одного сеанса пользователя реальной многопоточности нет. Это означает, что клиентское приложение 1С не может запустить параллельный поток для выполнения произвольного кода, а затем "дождаться" его завершения.
  3. Фоновые задания – это механизм платформы для выполнения длительных операций на сервере в отдельном процессе, независимом от клиентского сеанса. Они предназначены для тяжелых вычислений, обработки данных и других задач, которые могут занимать много времени и не должны блокировать работу пользователя. Фоновые задания являются основой для реализации серверной асинхронности.

Ключевое отличие: синтаксис Асинх/Ждать в 1С предназначен для работы с *системными* асинхронными операциями, доступными на клиенте. Он не предоставляет механизм для *произвольного* запуска кода в отдельном потоке или для прямого неблокирующего ожидания результатов серверного фонового задания с помощью одной конструкции Ждать.

Особенности работы Асинхронных функций в 1С и ограничение на возврат Обещания

Когда вы объявляете функцию как Асинх Функция, вы можете использовать внутри нее оператор Ждать для вызова других асинхронных (системных) функций. Такая функция всегда неявно возвращает Обещание. Однако существует критическое ограничение: вы не можете самостоятельно создать и вернуть объект Обещание из собственной асинхронной функции, чтобы связать его с произвольной логикой (например, с результатом фонового задания на сервере). Платформа 1С управляет созданием и разрешением Обещаний для своих встроенных асинхронных операций.

Это означает, что мы не можем просто обернуть вызов серверной фоновой функции в Обещание и затем ждать его разрешения на клиенте в желаемом синтаксисе.

Давайте посмотрим на пример исключения, который иллюстрирует, как работают Асинх функции и Попытка...Исключение:


Процедура П()
    Попытка
        // Вызываем асинхронную функцию, но НЕ используем Ждать.
        // Поэтому исключение не будет перехвачено здесь,
        // так как Обещание, содержащее исключение, не будет 'развернуто'.
        Функ1(); 
    Исключение
        // Исключение из Функ1() не будет перехвачено здесь
    КонецПопытки
КонецПроцедуры

Асинх Функция Функ1()
    ВызватьИсключение "Выброшено в Функ1()";
КонецФункции

В приведенном выше примере исключение, выброшенное в Функ1(), не будет перехвачено в блоке Попытка процедуры П(), если Ждать не используется. Это происходит потому, что Функ1() возвращает Обещание, которое переходит в состояние Failure, но сам вызов Функ1() является неблокирующим и не "разворачивает" это Обещание до его разрешения. Чтобы перехватить исключение, необходимо использовать Ждать:


Асинх Процедура П()
    Попытка
        // Теперь Ждать 'разворачивает' Обещание, и исключение будет перехвачено.
        Ждать Функ1(); 
    Исключение
        Сообщить("Исключение перехвачено: " + ОписаниеОшибки());
    КонецПопытки
КонецПроцедуры

Асинх Функция Функ1()
    ВызватьИсключение "Выброшено в Функ1()";
КонецФункции

Этот пример подчеркивает, что Асинх/Ждать имеет свою специфику обработки исключений, которая отличается от синхронного кода.

Как реализуются длительные операции на сервере с клиентским взаимодействием сегодня?

Для выполнения длительных серверных операций с возможностью отображения прогресса на клиенте и получением результата после завершения в 1С традиционно и весьма эффективно используется механизм "ДлительныеОперации" из Библиотеки стандартных подсистем (БСП). Этот механизм не использует синтаксис Асинх/Ждать для своих серверных операций, а опирается на следующие принципы:

  1. Запуск фонового задания: Клиент инициирует выполнение серверной функции, которая запускает фоновое задание на кластере серверов 1С.
  2. Опрос сервера (polling): Клиент не ждет в блокирующем режиме завершения фонового задания. Вместо этого он периодически опрашивает сервер о статусе выполнения фонового задания. Для этого используется механизм ОбработчикОжидания на клиенте. ОбработчикОжидания позволяет выполнять некоторую процедуру через определенные интервалы времени, не блокируя при этом пользовательский интерфейс. В эти интервалы клиент отправляет запрос на сервер, чтобы узнать, завершилось ли фоновое задание, и если да, то какой у него результат.
  3. Отображение прогресса: Во время ожидания клиент может отображать пользователю форму прогресса ("крутилка", сообщения о ходе выполнения), используя данные, которые фоновое задание может периодически записывать (например, в регистр сведений или временное хранилище).
  4. Получение результата (callbacks/оповещения): Когда фоновое задание завершается, клиент получает результат через механизм ОписаниеОповещения. Это процедура обратного вызова, которая будет выполнена на клиенте после получения окончательного статуса и результата от сервера.

Таким образом, сценарий выглядит так:


// Вместо желаемого:
// Результат = Ждать ВыполнитьФоново(ПутьФункции, ПараметрыФункции);

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

&НаКлиенте
Процедура ОбработатьРезультатДлительнойОперации(Результат, ДополнительныеПараметры) Экспорт
    Если Результат.Статус = "Выполнено" Тогда
        Сообщить("Операция успешно завершена. Результат: " + Результат.Значение);
    Иначе
        Сообщить("Операция завершена с ошибкой: " + Результат.ОписаниеОшибки);
    КонецЕсли;
КонецПроцедуры

Такой подход хоть и менее элегантен, чем желаемый Ждать, но является рабочим и широко используется в типовых конфигурациях. Его можно сравнить с функцией DoEvents() из старых версий Visual Basic, которая позволяла приложению обрабатывать события пользовательского интерфейса, не выходя из основного цикла выполнения, тем самым сохраняя отзывчивость. В 1С ОбработчикОжидания является улучшенной версией этой концепции, так как он не требует явного вызова в каждой точке кода, а работает в фоновом режиме. Для практической реализации такого алгоритма можно изучить примеры использования механизма длительных операций в актуальных версиях БСП.

Почему желаемый синтаксис сложно реализовать?

Архитектура 1С:Предприятия клиент-серверная. Клиент и сервер являются отдельными процессами, которые общаются по протоколу. Сервер сам по себе не может "уведомить" клиента о завершении фонового задания без предварительного запроса со стороны клиента. Это связано с тем, что клиент может быть, например, веб-клиентом в браузере, который имеет свои ограничения на двустороннюю связь.

Для реализации прямого Ждать для серверных фоновых заданий потребовалось бы одно из следующих решений:

  1. Реальная многопоточность на клиенте: Чтобы клиент мог запустить "фоновую" операцию на сервере, а сам в это время продолжать работать и "дождаться" результата, не блокируя UI, ему нужна была бы поддержка настоящей многопоточности. Это, как уже упоминали, гораздо более сложно, чем просто "синтаксический сахар", и повлечет за собой значительные изменения в архитектуре платформы.
  2. Механизм "таблицы мест пауз" или "корутин" на платформенном уровне для клиентских сеансов: Это позволило бы платформе "сохранять" состояние клиентского вызова, отправлять запрос на сервер и возобновлять выполнение клиентского кода после получения ответа, аналогично тому, как работают асинхронные вызовы в JavaScript. Этот подход уже частично используется для внутренних асинхронных операций 1С, но пока не доступен для произвольных пользовательских.
  3. Протокол push-уведомлений от сервера к клиенту: Если бы сервер мог сам инициировать связь с клиентом (например, через WebSockets для веб-клиента или какой-то другой механизм для толстого клиента), тогда можно было бы обойтись без постоянного опроса. Однако это также требует значительных изменений в протоколе взаимодействия и архитектуре.

Пока таких механизмов нет, клиент вынужден либо блокироваться (синхронный вызов серверной функции), либо опрашивать сервер (`ОбработчикОжидания`), либо использовать системные асинхронные операции (`Асинх`/`Ждать`) для внутренних клиентских действий.

Что можно сделать сегодня?

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

    Такая процедура запускает указанную серверную функцию в фоновом режиме, отображает "крутилку" на клиенте и, по завершении фонового задания (которое отслеживается через опрос), передает результат в процедуру-оповещение. Для реализации подобной схемы с нуля можно использовать пример реализации фоновых заданий с индикацией прогресса без БСП.

  4. Ждать развития платформы: Сообщество разработчиков активно обсуждает и ожидает дальнейшего развития асинхронных возможностей платформы, возможно, с появлением более гибких механизмов для пользовательской асинхронности в будущих версиях, таких как 8.4 или последующих. Например, добавление функции ПаузаАсинх(ВремяПаузы) на клиенте, которая позволяла бы приостанавливать выполнение асинхронной цепочки на клиенте с возвратом управления в основной поток и последующим возобновлением, могло бы стать шагом к реализации более гибких клиентских ожиданий.

Итоги

Синтаксис Асинх/Ждать в 1С — это мощный инструмент для организации неблокирующих операций на стороне клиента, значительно улучшающий отзывчивость пользовательского интерфейса. Однако он не является эквивалентом многопоточности или универсальным решением для прямого неблокирующего ожидания произвольных серверных фоновых заданий.

Для выполнения длительных серверных операций с контролем на клиенте и получением результата по-прежнему используются механизмы фоновых заданий в сочетании с ОбработчикомОжидания и ОписаниеОповещения, как это реализовано в "ДлительныхОперациях" БСП. Понимание этих различий позволяет эффективно применять существующие инструменты и строить надежные и отзывчивые приложения на платформе 1С.

← На главную