Многие разработчики 1С, знакомясь с новым синтаксисом Асинх/Ждать, сталкиваются с ожиданием, что он позволит легко и красиво организовать асинхронное выполнение любых пользовательских функций, особенно длительных серверных операций, с возможностью ожидания результата на клиенте в неблокирующем режиме. Однако на практике возможности этого синтаксиса несколько отличаются от первоначальных представлений. Давайте рассмотрим, почему это так, и какие механизмы следует использовать для решения подобных задач в текущей версии платформы 1С.
Итак, в платформе 1С появился синтаксис Асинх/Ждать, призванный упростить работу с асинхронностью, сделать код более читаемым, избавляя от "лапши" из колбэков и обработчиков оповещений. Естественным желанием многих разработчиков было использовать его для следующего сценария:
Результат = Ждать ВыполнитьФоново(ПутьФункции, ПараметрыФункции);
где ВыполнитьФоново была бы произвольной, созданной нами функцией, запускающей длительную операцию на сервере, а Ждать позволял бы клиенту не блокироваться, но при этом "дождаться" результата в той же точке кода, где был сделан вызов. К сожалению, напрямую в таком виде эта конструкция не работает для произвольных пользовательских функций.
Прежде всего, давайте внесем ясность в терминологию. Важно понимать, что асинхронность и многопоточность — это разные понятия. В 1С:
Асинх/Ждать) в первую очередь направлена на то, чтобы не блокировать основной поток выполнения на клиенте (UI-поток) во время длительных операций, которые происходят на самом клиенте (например, работа с файлами, диалоги, криптография). Это позволяет пользовательскому интерфейсу оставаться отзывчивым, пока операция выполняется в фоновом режиме на той же клиентской машине. Асинхронная функция всегда возвращает объект типа Обещание, который является оберткой для будущего результата. Оператор Ждать приостанавливает выполнение текущей асинхронной функции до тех пор, пока Обещание не будет разрешено (успешно завершено или отклонено с ошибкой).Ключевое отличие: синтаксис Асинх/Ждать в 1С предназначен для работы с *системными* асинхронными операциями, доступными на клиенте. Он не предоставляет механизм для *произвольного* запуска кода в отдельном потоке или для прямого неблокирующего ожидания результатов серверного фонового задания с помощью одной конструкции Ждать.
Когда вы объявляете функцию как Асинх Функция, вы можете использовать внутри нее оператор Ждать для вызова других асинхронных (системных) функций. Такая функция всегда неявно возвращает Обещание. Однако существует критическое ограничение: вы не можете самостоятельно создать и вернуть объект Обещание из собственной асинхронной функции, чтобы связать его с произвольной логикой (например, с результатом фонового задания на сервере). Платформа 1С управляет созданием и разрешением Обещаний для своих встроенных асинхронных операций.
Это означает, что мы не можем просто обернуть вызов серверной фоновой функции в Обещание и затем ждать его разрешения на клиенте в желаемом синтаксисе.
Давайте посмотрим на пример исключения, который иллюстрирует, как работают Асинх функции и Попытка...Исключение:
Процедура П()
Попытка
// Вызываем асинхронную функцию, но НЕ используем Ждать.
// Поэтому исключение не будет перехвачено здесь,
// так как Обещание, содержащее исключение, не будет 'развернуто'.
Функ1();
Исключение
// Исключение из Функ1() не будет перехвачено здесь
КонецПопытки
КонецПроцедуры
Асинх Функция Функ1()
ВызватьИсключение "Выброшено в Функ1()";
КонецФункции
В приведенном выше примере исключение, выброшенное в Функ1(), не будет перехвачено в блоке Попытка процедуры П(), если Ждать не используется. Это происходит потому, что Функ1() возвращает Обещание, которое переходит в состояние Failure, но сам вызов Функ1() является неблокирующим и не "разворачивает" это Обещание до его разрешения. Чтобы перехватить исключение, необходимо использовать Ждать:
Асинх Процедура П()
Попытка
// Теперь Ждать 'разворачивает' Обещание, и исключение будет перехвачено.
Ждать Функ1();
Исключение
Сообщить("Исключение перехвачено: " + ОписаниеОшибки());
КонецПопытки
КонецПроцедуры
Асинх Функция Функ1()
ВызватьИсключение "Выброшено в Функ1()";
КонецФункции
Этот пример подчеркивает, что Асинх/Ждать имеет свою специфику обработки исключений, которая отличается от синхронного кода.
Для выполнения длительных серверных операций с возможностью отображения прогресса на клиенте и получением результата после завершения в 1С традиционно и весьма эффективно используется механизм "ДлительныеОперации" из Библиотеки стандартных подсистем (БСП). Этот механизм не использует синтаксис Асинх/Ждать для своих серверных операций, а опирается на следующие принципы:
ОбработчикОжидания на клиенте. ОбработчикОжидания позволяет выполнять некоторую процедуру через определенные интервалы времени, не блокируя при этом пользовательский интерфейс. В эти интервалы клиент отправляет запрос на сервер, чтобы узнать, завершилось ли фоновое задание, и если да, то какой у него результат.ОписаниеОповещения. Это процедура обратного вызова, которая будет выполнена на клиенте после получения окончательного статуса и результата от сервера.Таким образом, сценарий выглядит так:
// Вместо желаемого:
// Результат = Ждать ВыполнитьФоново(ПутьФункции, ПараметрыФункции);
// Используется:
&НаКлиенте
Процедура ЗапуститьДлительнуюОперацию()
Параметры = Новый Структура("ПутьКФункции, ПараметрыФункции", "МояСервернаяФункция", Новый Структура("Ключ1", "Значение1"));
ОписаниеОповещенияОЗавершении = Новый ОписаниеОповещения("ОбработатьРезультатДлительнойОперации", ЭтотОбъект);
// Вызываем стандартный механизм БСП для длительных операций
// Он запустит фоновое задание и организует опрос/оповещение
ДлительныеОперацииКлиент.ВыполнитьВФоне(ОписаниеОповещенияОЗавершении, Параметры);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьРезультатДлительнойОперации(Результат, ДополнительныеПараметры) Экспорт
Если Результат.Статус = "Выполнено" Тогда
Сообщить("Операция успешно завершена. Результат: " + Результат.Значение);
Иначе
Сообщить("Операция завершена с ошибкой: " + Результат.ОписаниеОшибки);
КонецЕсли;
КонецПроцедуры
Такой подход хоть и менее элегантен, чем желаемый Ждать, но является рабочим и широко используется в типовых конфигурациях. Его можно сравнить с функцией DoEvents() из старых версий Visual Basic, которая позволяла приложению обрабатывать события пользовательского интерфейса, не выходя из основного цикла выполнения, тем самым сохраняя отзывчивость. В 1С ОбработчикОжидания является улучшенной версией этой концепции, так как он не требует явного вызова в каждой точке кода, а работает в фоновом режиме. Для практической реализации такого алгоритма можно изучить примеры использования механизма длительных операций в актуальных версиях БСП.
Архитектура 1С:Предприятия клиент-серверная. Клиент и сервер являются отдельными процессами, которые общаются по протоколу. Сервер сам по себе не может "уведомить" клиента о завершении фонового задания без предварительного запроса со стороны клиента. Это связано с тем, что клиент может быть, например, веб-клиентом в браузере, который имеет свои ограничения на двустороннюю связь.
Для реализации прямого Ждать для серверных фоновых заданий потребовалось бы одно из следующих решений:
Пока таких механизмов нет, клиент вынужден либо блокироваться (синхронный вызов серверной функции), либо опрашивать сервер (`ОбработчикОжидания`), либо использовать системные асинхронные операции (`Асинх`/`Ждать`) для внутренних клиентских действий.
ДлительныеОперацииКлиент и связанных с ним объектов в БСП.
Процедура ВыполнитьФункциюОтображаяПрогрессВыполнения(ПутьФункции, ПараметрыФункции, ОповещениеОбработкиРезультата, ЗаголовокФормыПрогресса = Неопределено, ВладелецФормыПрогресса = Неопределено)
// Логика запуска фонового задания, отображения формы прогресса,
// организации опроса сервера и вызова ОповещенияОбработкиРезультата
// после завершения фонового задания.
// Функция на сервере может быть такой:
// &НаСервереБезКонтекста
// Функция МояФоноваяФункция(ПараметрыФункции)
// // ... выполнение длительных операций ...
// Возврат Результат;
// КонецФункции
КонецПроцедуры
Такая процедура запускает указанную серверную функцию в фоновом режиме, отображает "крутилку" на клиенте и, по завершении фонового задания (которое отслеживается через опрос), передает результат в процедуру-оповещение. Для реализации подобной схемы с нуля можно использовать пример реализации фоновых заданий с индикацией прогресса без БСП.
ПаузаАсинх(ВремяПаузы) на клиенте, которая позволяла бы приостанавливать выполнение асинхронной цепочки на клиенте с возвратом управления в основной поток и последующим возобновлением, могло бы стать шагом к реализации более гибких клиентских ожиданий.Синтаксис Асинх/Ждать в 1С — это мощный инструмент для организации неблокирующих операций на стороне клиента, значительно улучшающий отзывчивость пользовательского интерфейса. Однако он не является эквивалентом многопоточности или универсальным решением для прямого неблокирующего ожидания произвольных серверных фоновых заданий.
Для выполнения длительных серверных операций с контролем на клиенте и получением результата по-прежнему используются механизмы фоновых заданий в сочетании с ОбработчикомОжидания и ОписаниеОповещения, как это реализовано в "ДлительныхОперациях" БСП. Понимание этих различий позволяет эффективно применять существующие инструменты и строить надежные и отзывчивые приложения на платформе 1С.