«Асинхронные методы в 1С», статья Василия Ханевича

На последней конференции Инфостарт в ноябре 2021 выступал с докладом по асинхронным методам наш автор Василий Ханевич (курсы по запросам, СКД, администрированию, расширениям и т.д.).

Считаем полезным поделиться детальным разбором их работы. Сядьте поудобнее, это реальный лонгрид на 53 страницы текста А4 :)

Что здесь важно – у 1С давно ничего не менялось в языке 1С, но сейчас появился новый тип данных (“Обещания”), новые асинхронные функции – и это само по себе интересно.

Впрочем, поехали!

Содержание

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

Навигация

 

Введение. Зачем вообще потребовались асинхронные методы в платформе “1С:Предприятие 8”?

Конфигурации на платформе “1С:Предприятие 8” могут работать на различных клиентских устройствах, например, через тонкий клиент на компьютерах под управлением ОС Windows, Linux, MacOS или через веб-клиент через Internet Explorer, Edge, Mozilla Firefox, Google Chrome или Safari. Каждый из этих вариантов имеет свои особенности – значит со стороны платформы должна быть реализована поддержка большого количества вариантов работы.

Стоит выделить поддержку работы через веб-клиент – удобный для пользователей режим, поскольку им не нужно устанавливать дополнительные программы.

Через браузер можно работать с почтой, облачными хранилищами, банковскими системами и огромным количеством других приложений. Поскольку важно обеспечить безопасность пользователя, надежность и высокую скорость, разработчики браузеров предъявляют жесткие требования к веб-сайтам и технологиям, к тому же эти требования меняются с течением времени.

Фирма “1С” уделяет большое внимание поддержке современных версий браузеров, в платформу вносятся изменения, которые диктуются разработчиками браузеров.

Рассмотрим несколько примеров.

Отказ от модальности

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

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

Поэтому рекомендуется использовать блокирующие окна вместо модальных. Блокирующее окно как бы “рисуется” поверх главного окна, блокируется только текущая вкладка браузера, в которой открыта конфигурация, позволяя переключаться на другие вкладки, поскольку модальные окна браузера при этом не используются.

Блокирующее окно

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

Асинхронные вызовы расширений и внешних компонент

Рассмотрим еще один пример, демонстрирующий, что требования браузеров могут влиять на программный код в конфигурациях на платформе “1С:Предприятие”.

В сентябре 2013 года было опубликовано, что с января 2014 года браузер Google Chrome по умолчанию будет блокировать плагины NPAPI, а в итоге поддержка NPAPI будет полностью удалена из Chrome. И действительно в сентябре 2015 года был выпущен Google Chrome (версия 45), который перестал поддерживать эту технологию на всех ОС. Аналогично поступили и разработчики других браузеров – Opera, Firefox, Safari, Internet Explorer теперь также не поддерживают NPAPI.

NPAPI (Netscape Plugin Application Programming Interface) – это технология разработки дополнительных модулей (плагинов) для различных браузеров. В качестве примеров можно привести Adobe Flash Player, Microsoft Silverlight, Java Runtime Environment.

В чем же причина отказа от поддержки этой технологии? Ее архитектура была разработана еще в 90-х годах XX века. На сегодняшний день она устарела, могла становиться причиной зависаний и сбоев, были выявлены проблемы, связанные с безопасностью. Кроме этого NPAPI не поддерживается на мобильных устройствах.

В качестве альтернативы компания Google предлагает применять технологию Native Messaging. Принципиально важной особенностью этой технологии является обязательное использование асинхронных методов.

Может возникнуть вопрос – какое отношение используемые в браузерах технологии могут иметь к программному коду на встроенном языке “1С:Предприятие”?

Дело в том, что при использовании веб-клиента для работы с файлами, криптографией, буфером обмена и внешними компонентами требуется установить расширение для браузера. Следовательно, это расширение должно поддерживать актуальные технологии. Поэтому фирма “1С” внесла изменения в платформу 8.3.5 и реализовала для методов встроенного языка, которые использовали технологию NPAPI, аналогичные асинхронные методы. Например, вместо метода ПолучитьФайлы теперь нужно применять НачатьПолучениеФайлов. А разработчикам конфигураций в очередной раз пришлось переработать программный код.

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

Синхронные методы

С самых первых версий платформы “1С:Предприятие” в каждом отдельном сеансе программный код, написанный на встроенном языке, исполняется строго последовательно. Это и называется синхронным исполнением программного кода, а соответствующие методы встроенного языка – синхронными.

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

Рассмотрим пример программного кода:

Счетчик = 1;
ТекстСообщения = "Сообщение";
Сообщить(ТекстСообщения);

Он выполняется пошагово:

  1. Первым делом будет выполнено присваивание переменной Счетчик значения 1.
  2. Осуществляется переход ко второй строке кода, выполняется операция присваивания.
  3. Осуществляется переход к третьей строке кода, выводится сообщение пользователю.

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

Здесь можно привести аналогию с лентой конвейера, которая по одной, друг за другом, поставляет детали для обработки на оборудовании:

Лента конвейера

Аналогично интерпретатор встроенного языка поочередно выполняет поступающие команды:

Инерпретатор встроенного языка

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

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

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

Курсор изменит свой вид

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

Пусть есть следующий программный код:

Ответ = Вопрос("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет);
Сообщить("Начало загрузки");

Метод Вопрос является синхронным, т.е. блокирует поток выполнения команд. Значит, платформа не перейдет к выполнению следующей команды (не выведет сообщение) до тех пор, пока пользователь не ответит на вопрос.

Сообщение на вопрос

При работе в тонком клиенте такое поведение не является проблемой, система будет ожидать ответа пользователя, после чего выполнение кода продолжится. Но при работе в веб-клиенте существует ряд ограничений, которые накладываются браузерами.

Веб-клиент написан на JavaScript. Движок JavaScript в браузере работает по следующей логике: он ожидает поступления задач к выполнению, поочередно, последовательно выполняет их и затем снова ожидает появления следующей задачи. Примерами задач может быть выполнение скрипта на веб-странице, выполнение обработчиков событий (нажатие пользователем кнопок на клавиатуре, движение мышью и т.д.). Подобный способ обработки задач называется событийным циклом (англ. – Event Loop).

Event Loop

JavaScript фактически является однопоточным. Если задача выполняется продолжительное время, браузер в это же время не сможет обрабатывать пользовательские события, возможно “зависание” браузера. Другими словами, в основном потоке может происходить что-то одно – или исполняется код на языке JavaScript, или браузер реагирует на действия пользователя.

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

Рассмотрим пример. Пусть есть такой HTML-документ:

<html>
<body>
    <button onclick="LongTermCalculation()">Длительная операция</button>
    <script type="text/javascript">
        function LongTermCalculation() {
            var res = 0;
            for (var i = 0; i < 10000000000; i++){
                  res += Math.sin(i);
            }
            alert(res);
        }
    </script>
</body>
</html>

На странице располагается кнопка, при нажатии на которую исполняется код на JavaScript. В этом коде на каждой итерации цикла вычисляется синус от значения счетчика цикла. Это достаточно длительная операция, поскольку требуется выполнить 10 миллиардов итераций цикла. Поэтому браузер периодически проверяет, не завис ли выполняемый скрипт, задает пользователю вопрос, не следует ли прервать его выполнение. Например, в Mozilla Firefox такой вопрос выглядит следующим образом:

Вопрос пользователю

Одним из способов решения описанной проблемы является использованием асинхронных методов, которые предназначены для предотвращения блокировки основного потока. Асинхронные методы могут быть реализованы в JavaScript, а также в других языках – Python, C#.

Рассмотрим асинхронные методы подробнее.

Асинхронные методы

Синхронный метод блокирует основной поток выполнения кода до тех пор, пока этот метод не завершится, включая все процедуры и функции, которые из этого метода вызываются. Браузеры, как было рассмотрено выше, имеют ограничения на выполнение синхронных методов. Например, современные браузеры не позволяют выполнять операции с файловой системой клиентского компьютера в синхронном режиме.

Поэтому были придуманы асинхронные методы – это такие методы, выполнение которых не происходит синхронно с кодом, из которого они вызваны. В этом случае нет синхронизации по времени между вызывающим и вызываемым кодом. Вызывающий метод не дожидается завершения вызванного метода, а продолжает свое выполнение после вызова асинхронного метода. За счет этого не блокируется основной поток выполнения кода.

Перепишем программный код на встроенном языке из прошлой главы, заменив синхронный вызов на асинхронный:

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

Метод Вопрос является синхронным, а метод ПоказатьВопрос – асинхронным. После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, до того, как сам асинхронный метод ПоказатьВопрос отработает. Таким образом, асинхронный метод не блокирует основной поток. Поэтому сообщение о начале загрузки будет показано еще до того, как пользователь ответит на вопрос.

Когда вызванный асинхронный метод завершит свою работу (пользователь ответит на заданный вопрос), он оповестит о своем завершении и предоставит доступ к результату (в данном случае это будет информация о том, какую кнопку в диалоговом окне нажал пользователь: Да или Нет). Это событие можно обработать – в зависимости от нажатой пользователем кнопки выполнить то или иное действие.

Показать вопрос

Для того, чтобы система знала, что нужно сделать при завершении работы асинхронного метода, первым параметром метода ПоказатьВопрос указывается специальный объект встроенного языка ОписаниеОповещения, которое и представляет собой описание процедуры, которую нужно вызвать после того, как пользователь ответит на вопрос. Подробнее про этот объект поговорим в следующем разделе.

Асинхронные методы встроенного языка можно использовать, например, в следующих сценариях:

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

В качестве примера асинхронных методов встроенного языка можно привести клиентские методы для работы с файлами, поскольку в веб-клиенте доступны только асинхронные методы работы с файлами. Поэтому вместо синхронного метода КопироватьФайл следует использовать асинхронные аналоги НачатьКопированиеФайла или КопироватьФайлАсинх, вместо синхронного метода ПолучитьФайлы – НачатьПолучениеФайловССервера или ПолучитьФайлыССервераАсинх и т.д.

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

JavaScript имеет развитый функционал для работы с асинхронными методами. Особенно часто асинхронный код применяется при получении данных или для доступа к внешним устройствам – получение файлов с удаленных серверов, получение данных из базы, доступ к видео с веб-камеры.

Таким образом, некоторые приемы программирования на JavaScript теперь можно применять при разработке на платформе “1С:Предприятие”. Во встроенном языке существуют схожие способы реализации асинхронности, которые уже давно присутствуют в JavaScript:

  • Использование методов обратного вызова (callback)
  • Использование Обещаний (promise)

В следующих главах подробно рассмотрим эти приемы.

Использование методов обратного вызова

Принцип работы методов обратного вызова

После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, не дожидаясь, когда асинхронный метод завершит свою работу. Когда вызванный асинхронный метод отработает, он оповестит о своем завершении и предоставит доступ к результату. Оповещение о завершении происходит при помощи вызова процедуры, описание которой передается в асинхронно вызываемый метод. Эта процедура и называется методом обратного вызова (англ. callback – дословно “обратный звонок”), поскольку позволяет асинхронному методу “связаться” с вызвавшим его кодом:

Вызов асинхронного метода

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

Пусть есть следующий код на языке JavaScript:

console.log('1');
console.log('2');
console.log('3');

Этот код последовательно выводит в консоль числа 1, 2 и 3. Это синхронный код.

Теперь модифицируем его – реализуем вывод числа 2 в консоль по таймеру, через 2 секунды:

console.log('1');
setTimeout(LogTwoSecondsLater, 2000);
console.log('3');

function LogTwoSecondsLater() {
    console.log('2')
}

Функция setTimeout ожидает 2 секунды (вторым параметром функции указан период ожидания в миллисекундах: 2000 мс = 2 с), а затем вызывает функцию обратного вызова LogTwoSecondsLater, переданную в качестве первого параметра. Функция setTimeout не блокирует основной поток. Скрипт не “зависнет” на 2 секунды, а продолжит выполнение. В консоль будут выведены числа 1, 3. И только через 2 секунды, когда сработает таймер, будет вызвана функция LogTwoSecondsLater, в консоль будет выведено число 2.

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

Процедура ДлительнаяЗагрузка()
    ЗагрузитьАсинхронно(ВыполнитьДействияПослеЗагрузки);
КонецПроцедуры

Процедура ЗагрузитьАсинхронно(МетодОбратногоВызова)
    //Действия для загрузки данных...
    МетодОбратногоВызова();
КонецПроцедуры

Процедура ВыполнитьДействияПослеЗагрузки()
    //Оповещение, что загрузка завершена. Действия после завершения загрузки...
КонецПроцедуры

Во встроенном языке асинхронный код с использованием методов обратного вызова реализован при помощи объекта ОписаниеОповещения. Этот объект содержит описание метода обратного вызова:

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

Для того, чтобы правильно составить описание метода обратного вызова, корректно описать все необходимые параметры, удобно использовать Синтакс-помощник. В нем указаны все параметры метода обратного вызова, который будет выполнен при завершении асинхронного метода.

Например, для метода ПоказатьВопрос после ответа пользователя на вопрос будет вызвана процедура со следующими параметрами:

  • РезультатВопроса
  • ДополнительныеПараметры

Глобальный контекст

Пример 1 – Организация диалога с пользователем

Рассмотрим пример. В нем задается вопрос пользователю, на который он может ответить Да или Нет. После того, как пользователь ответит на вопрос, в сообщении выводим, сколько времени ему потребовалось на ответ.

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

&НаКлиенте
Процедура ЗадатьВопрос(Команда)

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

КонецПроцедуры

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

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

КонецПроцедуры

Выделим важные моменты в этом коде:

  1. Используется асинхронный метод ПоказатьВопрос. В нем первым параметром передается описание оповещения. Оно сообщает системе, что после того, как пользователь ответит на вопрос, нужно вызвать метод ПоказатьВопросЗавершение, расположенный в модуле этой же формы, и передать в него ДопПараметры.
  2. Методы обратного вызова могут располагаться в модуле формы, общем модуле или в модуле команды. Расположение метода указывается вторым параметром в конструкторе объекта ОписаниеОповещения.
  3. Метод ПоказатьВопросЗавершение обязательно должен быть экспортным, даже если он располагается в модуле той же формы. Если не указать ключевое слово Экспорт, то будет выдана ошибка:

Метод ПоказатьВопросЗавершение

  1. Поскольку метод ПоказатьВопрос – асинхронный, после вызова этого метода управление вернется к коду, вызвавшему его, не дожидаясь, когда асинхронный метод завершит свою работу. Поэтому сообщение о начале загрузки появится до того, как пользователь ответит на вопрос. В этом заключается важное отличие асинхронного метода от синхронного. Синхронный метод Вопрос блокирует поток выполнения команд, поэтому следующая команда может быть выполнена только после того, как пользователь ответит на вопрос.
  2. Если нужно выполнять какие-то действия после того, как пользователь ответит на вопрос, то эти действия нужно выполнять в методе ПоказатьВопросЗавершение, а не сразу после вызова метода ПоказатьВопрос. Это также связано с тем, что метод ПоказатьВопрос является асинхронным.
  3. После ответа пользователя на вопрос будет вызвана отдельная процедура. Может потребоваться передать в нее какие-то данные, необходимые для дальнейшей работы. Для этого можно использовать ДополнительныеПараметры – второй параметр метода ПоказатьВопросЗавершение. В нашем примере нужно знать время начала, когда пользователю был показан вопрос. Поэтому время начала передается в метод обратного вызова через ДополнительныеПараметры.

Обратите внимание, что имена асинхронных процедур, которые используют методы обратного вызова, начинаются со слов Начать или Показать. Приведем в таблице несколько примеров – имя синхронного метода и соответствующего ему асинхронного:

Синхронный метод Асинхронный метод
Вопрос ПоказатьВопрос
Предупреждение ПоказатьПредупреждение
ОткрытьЗначение ПоказатьЗначение
КопироватьФайл НачатьКопированиеФайла
ВвестиЧисло ПоказатьВводЧисла
НайтиФайлы НачатьПоискФайлов
ПолучитьФайл НачатьПолучениеФайлаССервера

Пример 2 – Сохранение файла на клиентском компьютере

Рассмотрим более сложный пример.

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

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

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

&НаСервере
Функция СформироватьФайлНаСервере()

    ИмяФайла = ПолучитьИмяВременногоФайла("xlsx");

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

КонецФункции

&НаКлиенте
Процедура ОповещениеПослеВыбораФайлаДляЗаписи(ВыбранныеФайлы, ДополнительныеПараметры) Экспорт
    
    //3. Получение данных из временного хранилища, сохранение с выбранным именем
    Если ТипЗнч(ВыбранныеФайлы) = Тип("Массив") Тогда
        
        Если ТипЗнч(ДополнительныеПараметры) = Тип("Структура") Тогда
            
            АдресФайлаВоВременномХранилище = Неопределено;
            Если ДополнительныеПараметры.Свойство("АдресФайлаВоВременномХранилище", АдресФайлаВоВременномХранилище) Тогда
                
                ОписаниеОповещения = Новый ОписаниеОповещения("ОповещениеПослеПолученияФайла", ЭтотОбъект);        
                НачатьПолучениеФайлаССервера(ОписаниеОповещения, АдресФайлаВоВременномХранилище, ВыбранныеФайлы[0]);
                
            КонецЕсли;
            
        КонецЕсли;        
        
    КонецЕсли;
    
КонецПроцедуры

&НаКлиенте
Процедура ОповещениеПослеПолученияФайла(ПолученныйФайл, ДополнительныеПараметры) Экспорт
    
    //4. Уведомление пользователя о сохраненном файле
    Состояние("Записан файл " + ПолученныйФайл.ПолноеИмя);        
    
КонецПроцедуры

Разберем подробнее основные моменты в приведенном программном коде:

  1. На сервере в каталоге временных файлов создаем файл с уникальным именем и расширением xlsx. Заполняем его данными и помещаем во временное хранилище. Адрес во временном хранилище передаем на клиент, чтобы была возможность доступа к этим данным из клиентских процедур.
  2. Открываем диалог сохранения файла. Устанавливаем фильтр для диалога, чтобы пользователь мог сохранить файл с правильным расширением. Для отображения диалога используется асинхронный метод Показать, которому в параметре передается описание оповещения. Оно указывает системе, что после выбора пользователем имени файла будет вызвана процедура ОповещениеПослеВыбораФайлаДляЗаписи из модуля этой же формы, в эту процедуру будет передан адрес во временном хранилище, где располагаются данные требуемого файла.
  3. Вызывается метод НачатьПолучениеФайлаССервера – инициируется получение файла с сервера и сохранение его на клиентском компьютере под указанным пользователем именем. В качестве параметра передается еще одно описание оповещения. Оно указывает, что после завершения получения файла с сервера будет вызвана процедура ОповещениеПослеПолученияФайла.
  4. После того, как загрузка файла с сервера на клиентский компьютер закончена, система уведомляет пользователя, что загрузка файла завершена.

Схематично изобразим последовательность вызовов методов из приведенного фрагмента кода:

Последовательность вызовов методов

Вложенные процедуры с асинхронными методами

В предыдущих примерах описание оповещения передавалось в качестве параметра в асинхронный метод. Когда асинхронный метод завершал свою работу (пользователь ответил на вопрос, файл с сервера был сохранен на клиентский компьютер и т.д.), платформа вызывала метод, указанный в описании оповещения. Явного вызова обработчиков оповещений из программного кода не происходило.

Во встроенном языке есть метод ВыполнитьОбработкуОповещения, который используется для того, чтобы явно выполнить метод обратного вызова, указанный в качестве параметра. Такое действие может потребоваться в сложных случаях, когда асинхронные методы “вложены” друг в друга, и после цепочки асинхронных вызовов нужно выполнить действие, определенное раньше, еще до начала этих асинхронных вызовов.

В качестве примера рассмотрим следующую задачу. Необходимо открыть форму для выгрузки данных во внешнюю систему. Выгрузку можно производить только в том случае, когда в константе Лицензия заполнены данные лицензии для выгрузки. Если лицензия не заполнена, нужно предложить пользователю ввести данные лицензии, сохранить их в константе, после чего открыть форму для выгрузки данных во внешнюю систему.

Для начала рассмотрим синхронный код, решающий поставленную задачу:

&НаКлиенте
Процедура ОтправитьВоВнешнююСистему(Команда)
    
    ДанныеДляВнешнейСистемы = Новый Структура;
    ДанныеДляВнешнейСистемы.Вставить("Дата",        ТекущаяДата());
    ДанныеДляВнешнейСистемы.Вставить("Описание",    "ОписаниеПлатежа");
    //...
    
    Если ПроверитьЛицензию() Тогда
        //Открыть форму подключения к внешней системе
        ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы);
    Иначе
        
        ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии.
            |Ввести сейчас?";
        Ответ = Вопрос(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
        Если Ответ = КодВозвратаДиалога.Да Тогда
            
            Лицензия = "";
            ВвестиСтроку(Лицензия, "Лицензия");
            
            Если НЕ ПустаяСтрока(Лицензия) Тогда
                СохранитьЛицензию(Лицензия);
                //Открыть форму подключения к внешней системе
                ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы);
            КонецЕсли;
            
        КонецЕсли;
    
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПроверитьЛицензию()

    Лицензия = Константы.Лицензия.Получить();
    Возврат ЗначениеЗаполнено(Лицензия);

КонецФункции

&НаСервереБезКонтекста
Процедура СохранитьЛицензию(Лицензия)

    Константы.Лицензия.Установить(Лицензия);

КонецПроцедуры

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

Получится следующий асинхронный код:

&НаКлиенте
Процедура ОтправитьВоВнешнююСистему(Команда)
    
    ДанныеДляВнешнейСистемы = Новый Структура;
    ДанныеДляВнешнейСистемы.Вставить("Дата",        ТекущаяДата());
    ДанныеДляВнешнейСистемы.Вставить("Описание",    "ОписаниеПлатежа");
    //...
    
    ОбработкаПродолжения = Новый ОписаниеОповещения("ОтправитьВоВнешнююСистемуПродолжение", ЭтотОбъект, ДанныеДляВнешнейСистемы);
    НачатьПроверкуЛицензииИПодключениеКВнешнейСистеме(ОбработкаПродолжения);
    
КонецПроцедуры

&НаКлиенте
Процедура НачатьПроверкуЛицензииИПодключениеКВнешнейСистеме(ОбработкаПродолжения) Экспорт
    
    Если ПроверитьЛицензию() Тогда
        ВыполнитьОбработкуОповещения(ОбработкаПродолжения, Истина);
    Иначе
        ПоказатьВопросВводаЛицензии(ОбработкаПродолжения);
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПроверитьЛицензию()

    Лицензия = Константы.Лицензия.Получить();
    Возврат ЗначениеЗаполнено(Лицензия);

КонецФункции

&НаКлиенте
Процедура ПоказатьВопросВводаЛицензии(ОбработкаЗавершения)

    ПараметрыЗавершения = Новый Структура;
    ПараметрыЗавершения.Вставить("ОбработкаЗавершения", ОбработкаЗавершения);
    
    ОбработкаОтвета = Новый ОписаниеОповещения("ПоказатьВопросВводаЛицензииЗавершение", ЭтотОбъект, ПараметрыЗавершения);
    
    ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии.
        |Ввести сейчас?";
    ПоказатьВопрос(ОбработкаОтвета, ТекстВопроса, РежимДиалогаВопрос.ДаНет);

КонецПроцедуры

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

&НаКлиенте
Процедура ВводЛицензииЗавершение(ВведеннаяСтрока, Параметры) Экспорт

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

КонецПроцедуры

&НаСервереБезКонтекста
Процедура СохранитьЛицензию(Лицензия)

    Константы.Лицензия.Установить(Лицензия);

КонецПроцедуры

&НаКлиенте
Процедура ОтправитьВоВнешнююСистемуПродолжение(ЕстьЛицензия, ДанныеДляВнешнейСистемы) Экспорт
    
    Если НЕ ЕстьЛицензия Тогда
        Возврат;
    КонецЕсли;
    
    //Открыть форму подключения к внешней системе
    ОткрытьФорму("Обработка.ПодключениеКВнешнейСистеме.Форма", ДанныеДляВнешнейСистемы);

    
КонецПроцедуры

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

Получается, что из-за асинхронности код состоит из логически взаимосвязанных отдельных процедур. Переход к открытию формы подключения к внешней системы может производиться из разных точек алгоритма. Поэтому в этих точках нужно знать данные для выгрузки во внешнюю систему и результат проверки лицензии.

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

Из приведенного кода видно, что использование методов обратного вызова усложняет разработку. Рассмотрим, какие сложности встречаются при написании асинхронного кода и какие способы упрощения может предложить платформа “1С:Предприятие”.

Сложности при использовании методов обратного вызова

В языках программирования, в которых можно передавать функцию как параметр другой процедуры, например, в JavaScript, есть такой термин, как callback hell (англ. – ад обратных вызовов) – многократно вложенные друг в друга блоки. В качестве примера можно привести следующий код:

function callbackHell(){
    step1(function() {
        step2(function() {
            step3(function() {
                step4(function() {
                    step5(function() {
                        //...
                    });
                });
            });
        });
    });
}

Подобные конструкции делают код сложным для чтения, анализа и поддержки.

Во встроенном языке нет возможности указать функцию в качестве параметра другого метода, поэтому не удастся создать точно такой же код. Но использование методов обратного вызова усложняет код, делает его фрагментированным. Получается своего рода аналог callback hell для встроенного языка “1С:Предприятие”.

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

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

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

  • Выбрать кассу, по которой нужно произвести закрытие
  • Подтвердить закрытие смены
  • Проверить наличие непробитых чеков
  • Запросить сумму выемки денежных средств
  • Выполнить сверку итогов по картам на эквайринговом терминале
  • Заархивировать чеки за текущую смену, сформировать отчет о розничных продажах
  • Создать документ выемки денежных средств, отправить команду выемки на ККТ
  • Сформировать Z-отчет на ККТ
  • Проверить наличие ошибок ККТ при формировании Z-отчета
  • Зафиксировать время закрытия кассовой смены в базе
  • Передать документы в ЕГАИС
  • Открыть формы сформированных документов
  • Вывести сообщение с итоговой информацией о продажах и возвратах

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

Подобный код со своими нюансами есть во многих типовых конфигурациях – УТ, БП и т.д. За счет наличия в коде асинхронных методов код типовых конфигураций усложнился – его тяжелее и создавать, и отлаживать.

Поэтому в следующей главе рассмотрим инструменты платформы для упрощения написания кода.

Инструменты рефакторинга

В конфигураторе есть набор инструментов, которые призваны упростить работу с асинхронными методами. Команды доступны в меню конфигуратора Текст – Рефакторинг, а также в контекстном меню текстового редактора в подменю Рефакторинг:

Подменю Рефакторинг

Рассмотрим их подробнее.

Создать обработку оповещения

При создании описания метода обратного вызова нужно корректно указать все параметры этого метода. Конечно, можно посмотреть их в Синтакс-помощнике и вручную составить описание метода, но быстрее и удобнее будет, если платформа сама сгенерирует такой метод. И такая возможность есть.

Если установить курсор на имени асинхронного метода, после которого следует открывающаяся скобка, например, ПоказатьВопрос, НачатьПеремещениеФайла и т.д., то в меню станет доступна команда Создать обработку оповещения, которая самостоятельно сформирует описание оповещения и метод обратного вызова.

Рассмотрим пример. Есть фрагмент кода, в котором для метода ПоказатьВопрос не указан первый параметр – обработка оповещения:

&НаКлиенте
Процедура ЗагрузитьДанные()

    ПоказатьВопрос(, "Выполнить загрузку?", РежимДиалогаВопрос.ДаНет);
    
КонецПроцедуры

Установим курсор на метод ПоказатьВопрос и вызовем команду Создать обработку оповещения. Получим следующий код:

&НаКлиенте
Процедура ЗагрузитьДанные()

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

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

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

Преобразовать вызов

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

Рассмотрим пример. Есть фрагмент кода:

&НаКлиенте
Процедура ЗагрузитьДанные()

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

Установим курсор на метод Вопрос и вызовем команду Преобразовать вызов. Получим следующий код с использованием асинхронного метода ПоказатьВопрос:

&НаКлиенте
Процедура ЗагрузитьДанные()

    Ответ = Неопределено;


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

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

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

Найти вызовы модуля

Команда Преобразовать вызов работает только с одним участком кода. Часто требуется проанализировать целый модуль, найти в нем “нерекомендуемые синхронные методы”, чтобы заменить их на асинхронные аналоги. Для этого существует команда Найти вызовы модуля. Найденные строки кода, содержащие “нерекомендуемые синхронные вызовы”, выводятся в окно с результатами поиска:

Результаты поиска

Анализ нерекомендуемых синхронных вызовов конфигурации

Команда Найти вызовы модуля ищет синхронные методы только в одном модуле. Если нужно проанализировать такие вызовы во всей конфигурации, то можно воспользоваться командой в меню Конфигурация – Рефакторинг – Анализ нерекомендуемых синхронных вызовов конфигурации:

Анализ выводов

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

Таким образом, фирма “1С” не “спрятала” асинхронность от программистов на уровне платформы. Нужно разобраться с этой функциональностью. Для реализации асинхронности с использованием методов обратного вызова следует применять асинхронные методы (вместо Вопрос – ПоказатьВопрос, вместо Предупреждение – ПоказатьПредупреждение и т.д.). Если же в существующем коде встречаются синхронные вызовы, его нужно переработать – заменить синхронные методы на соответствующие им асинхронные. Для упрощения разработки можно использовать рассмотренные инструменты конфигуратора по рефакторингу кода.

В следующей главе рассмотрим новый вариант реализации асинхронности, который появился в платформе 8.3.18. Это асинхронность с использованием обещаний.

Использование обещаний

Использование методов обратного вызова усложняет программный код, делает его фрагментированным, сложным для восприятия и доработки. Синхронный код, в котором выполняемые команды расположены последовательно, друг за другом, создается и анализируется гораздо проще. Но для работы в веб-клиенте требуется именно асинхронный принцип выполнения операций. Как разрешить это противоречие – упростить написание кода и выполнить требования, предъявляемые браузерами?

В других языках программирования, таких как JavaScript или Python, существует тип данных Promise (англ. – обещание) и ключевые слова async/await. Использование этих конструкций делает асинхронный код простым, читаемым и удобным для доработки.

Похожий подход был реализован фирмой “1С” в платформе 8.3.18. Причем новый функционал не завязан на режим совместимости конфигурации, т.е. режим совместимости может быть установлен, например, в значение Версия 8.3.6, но при использовании платформы 8.3.18 и новее возможно использовать функционал асинхронности через обещания.

Важно отметить, что сам принцип использования асинхронности в платформе остался прежним. По-прежнему асинхронность доступна для работы с блокирующими окнами, файлами и каталогами, механизмами криптографии и внешними компонентами. Изменился только “внешний вид” программного кода, другими словами – способ указания платформе, какой именно код нужно выполнить после завершения асинхронного метода. Главная задача нововведения – приблизить асинхронный код к обычному последовательному коду, уменьшить сложность разработки.

Рассмотрим далее подробнее новые возможности платформы.

Новый тип – Обещание

В платформе 8.3.18 появился новый тип данных – Обещание. Объект такого типа предназначен для организации асинхронного кода.

Обещание представляет собой “хранилище” для пока что еще неизвестного результата выполнения некоторого действия (асинхронной функции).

Почему используется термин Обещание? Вспомним, что при вызове асинхронного метода поток выполнения не прерывается, управление возвращается к коду, вызвавшему этот метод. Но на выполнение самого асинхронного метода требуется время (пока пользователь ответит на вопрос или пока файл будет передан с сервера на клиент). Во время выполнения асинхронного метода нельзя сказать, какой результат он вернул, поскольку метод еще не завершился. Как только асинхронный метод завершится, он уведомит о своем выполнении и предоставит доступ к результату своей работы.

Получается, что при вызове асинхронной функции она должна сразу вернуть управление вызвавшему ее коду, не блокируя поток выполнения. Значит, она должна вернуть какое-то значение. Но какое? Ведь на выполнение функции потребуется время, а результат выполнения еще неизвестен в данный момент. Значит, асинхронная функция не может сразу вернуть результат выполнения, но может “пообещать” вернуть его в указанную переменную, как только функция завершит работу. В этом и заключается смысл использования Обещаний.

Поэтому асинхронная функция во встроенном языке всегда возвращает значение типа Обещание, которое может находиться в одном из трех состояний:

  1. Ожидание (Pending) – начальное состояние. Асинхронная функция еще не завершила свое выполнение, результат еще неизвестен.
  2. Завершено успешно (Success/Fulfilled). Асинхронная функция отработала успешно. В таком случае Обещание содержит результат выполнения асинхронной функции.
  3. Завершено с ошибкой (Failure/Rejected). При выполнении асинхронной функции было вызвано исключение, не обработанное программно. В таком случае Обещание содержит исключение.

Таким образом, Обещание из начального состояния может перейти в одно из двух финальных состояний: или “Завершено успешно”, или “Завершено с ошибкой” в зависимости от результата работы асинхронной функции:

Обещание

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

Объект типа Обещание доступен только в клиентском контексте: в тонком, толстом, мобильном клиентах и в веб-клиенте. На сервере этот объект недоступен. Это связано с тем, что асинхронные методы во встроенном языке создавались для использования только на клиенте.

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

Асинхронные функции

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

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

В платформе 8.3.18 появились новые асинхронные функции, возвращающие Обещание. Имена таких функций заканчиваются на Асинх.

Приведем в таблице несколько примеров – имя синхронного метода, соответствующую ему асинхронную процедуру и новую асинхронную функцию:

Синхронный метод Асинхронная процедура, использующая метод обратного вызова Асинхронная функция, возвращающая Обещание
Вопрос ПоказатьВопрос ВопросАсинх
Предупреждение ПоказатьПредупреждение ПредупреждениеАсинх
ОткрытьЗначение ПоказатьЗначение ОткрытьЗначениеАсинх
КопироватьФайл НачатьКопированиеФайла КопироватьФайлАсинх
ВвестиЧисло ПоказатьВводЧисла ВвестиЧислоАсинх
НайтиФайлы НачатьПоискФайлов НайтиФайлыАсинх
ПолучитьФайл НачатьПолучениеФайлаССервера ПолучитьФайлССервераАсинх

При помощи Синтакс-помощника для синхронного метода можно найти соответствующие ему асинхронные методы:

Синтакс-помощник

Сравним, например, методы для отображения вопроса пользователю. Отметим их сходства и отличия.

Синтаксис этих методов выглядит следующим образом:

Вопрос(<ТекстВопроса>, <Кнопки>, <Таймаут>, <КнопкаПоУмолчанию>, <Заголовок>, <КнопкаТаймаута>)
ПоказатьВопрос(<ОписаниеОповещенияОЗавершении>, <ТекстВопроса>, <Кнопки>, <Таймаут>, <КнопкаПоУмолчанию>, <Заголовок>, <КнопкаТаймаута>)
ВопросАсинх(<ТекстВопроса>, <Кнопки>, <Таймаут>, <КнопкаПоУмолчанию>, <Заголовок>, <КнопкаТаймаута>)

Вопрос и ВопросАсинх являются функциями.

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

Новая функция ВопросАсинх, появившаяся в платформе 8.3.18, возвращает объект типа Обещание. Перечень параметров у этой функции такой же, как и у синхронного метода Вопрос.

Процедура ПоказатьВопрос и функция ВопросАсинх выполняются асинхронно. Ответ пользователя при использовании процедуры ПоказатьВопрос будет передан в первом параметре метода обратного вызова. При использовании функции ВопросАсинх тот же ответ пользователя будет содержаться в объекте типа Обещание.

Отметим, что не для всех синхронных методов реализованы аналогичные функции с модификатором Асинх. Например, для метода формы ОткрытьМодально соответствующая асинхронная функция не существует. Поэтому следует использовать процедуру ОткрытьФорму с указанием обработчика оповещения:

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

&НаКлиенте
Процедура ОткрытиеФормыЗавершение(РезультатЗакрытия, ДополнительныеПараметры) Экспорт
    //Обработка результата...
КонецПроцедуры

Работа с асинхронными функциями

Асинхронная функция возвращает значение типа Обещание. Чтобы из этого значения получить результат работы асинхронной функции, нужно воспользоваться оператором Ждать, который также появился в платформе 8.3.18. Единственным параметром оператора Ждать является объект типа Обещание:

Результат = Ждать Обещание;

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

В коде может встретиться вот такая строка:

Ждать 1000;

Что она означает? Может показаться, что это способ организации паузы во встроенном языке, а 1000 – это длительность ожидания в миллисекундах. Но на самом деле это совсем не так. Если параметром оператора Ждать является любое значение, кроме Обещания, то платформа создаст Обещание, в котором будет “спрятано” указанное значение (в данном случае – число 1000). Затем созданное Обещание будет передано как параметр оператора Ждать. Результатом такого оператора является число 1000, но этот результат не присваивается другой переменной, поэтому в строке кода опущен оператор присваивания:

Результат = Ждать 1000;

Так что указанная строка – это синтаксически корректный программный код, но не способ организации паузы.

Модификатор Асинх

Имена асинхронных функций платформы, возвращающих Обещание, заканчиваются на Асинх. Если разработчику нужно реализовать собственную процедуру или функцию, которая будет работать асинхронно, ее нужно описать с использованием модификатора Асинх:

&НаКлиенте
Асинх Процедура ИмяПроцедуры()
    
КонецПроцедуры

&НаКлиенте
Асинх Функция ИмяФункции()
    
КонецФункции

Выделим несколько принципиально важных особенностей процедур или функций с модификатором Асинх.

Только внутри процедур или функций с модификатором Асинх можно использовать оператор Ждать. Также важно, что модификатор Асинх можно использовать только для клиентских процедур и функций. Если внутри обработчика клиентского события планируется использовать оператор Ждать, то такой обработчик также нужно описать с модификатором Асинх.

В процедурах и функциях с модификатором Асинх параметры всегда передаются по значению. При этом неважно, будет ли в описании метода использоваться ключевое слово Знач перед параметром или нет. Поэтому два указанных ниже описания асинхронных процедур являются абсолютно одинаковыми, параметр будет передан по значению:

Асинх Процедура ЗадатьВопрос(Знач РежимДиалога)
Асинх Процедура ЗадатьВопрос(РежимДиалога)

Функция с модификатором Асинх всегда возвращает Обещание. Но по правилам встроенного языка функция должна возвращать то значение, которое разработчик указывает в качестве параметра оператора Возврат. Функция без модификатора Асинх именно так и делает – возвращает значение параметра оператора Возврат. Функция с модификатором Асинх возвращает Обещание. Если такая функция завершится успешно, без генерации исключения, то в Обещании будет “спрятано” возвращаемое значение. Чтобы из Обещания получить конкретное значение, нужно воспользоваться оператором Ждать:

&НаКлиенте
Асинх Процедура ВывестиДанныеИзВнешнейСистемы(Команда)
    
    Результат = Ждать ПолучитьЗначениеИзВнешнейСистемы();
    
КонецПроцедуры

&НаКлиенте
Асинх Функция ПолучитьЗначениеИзВнешнейСистемы()

    //Здесь должен располагаться программный код для получения данных...
    Возврат Значение;

КонецФункции

Если функция с модификатором Асинх во время своего выполнения вызвала исключение, то в Обещании будет “спрятано” это исключение.

Поговорим про перехват исключений подробнее, здесь тоже есть несколько важных нюансов.

Обработка исключений в асинхронных процедурах и функциях

Исключение, возникшее в процедуре или функции с модификатором Асинх, не отобразится пользователю. Например, вот в таком программном коде:

&НаКлиенте
Процедура ВывестиДанныеИзВнешнейСистемы(Команда)
    
    ПолучитьЗначениеИзВнешнейСистемы();
    
КонецПроцедуры

&НаКлиенте
Асинх Функция ПолучитьЗначениеИзВнешнейСистемы()

    Возврат 5/0;//деление на ноль

КонецФункции

Аналогично в таком коде выполнение никогда не попадет в секцию Исключение, именно потому что функция ПолучитьЗначениеИзВнешнейСистемы является асинхронной. Исключение, вызванное в такой функции, не возникнет в вызывающем коде:

&НаКлиенте
Процедура ВывестиДанныеИзВнешнейСистемы(Команда)
    
    Попытка        
        ПолучитьЗначениеИзВнешнейСистемы();        
    Исключение        
        Сообщить(ОписаниеОшибки());//в эту строку не попадем        
    КонецПопытки;
    
КонецПроцедуры


&НаКлиенте
Асинх Функция ПолучитьЗначениеИзВнешнейСистемы()

    Возврат 5/0;//деление на ноль

КонецФункции

Чтобы в вызывающем коде обработать исключение, возникшее в функции с модификатором Асинх, нужно Обещание, возвращаемое такой функцией, использовать в операторе Ждать:

&НаКлиенте
Асинх Процедура ВывестиДанныеИзВнешнейСистемы(Команда)
    
    Попытка
        Ждать ПолучитьЗначениеИзВнешнейСистемы();
    Исключение
        Сообщить(ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

&НаКлиенте
Асинх Функция ПолучитьЗначениеИзВнешнейСистемы()

    Возврат 5/0;

КонецФункции

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

Исключение

&НаКлиенте
Процедура ВывестиДанныеИзВнешнейСистемы(Команда)
    
    Попытка
        ПолучитьЗначениеИзВнешнейСистемы();
    Исключение
        Сообщить(ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

&НаКлиенте
Асинх Процедура ПолучитьЗначениеИзВнешнейСистемы()

    А = 5/0;

КонецПроцедуры

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

Примеры использования асинхронных функций

Организация диалога с пользователем

Необходимо задать вопрос пользователю, на который он может ответить Да или Нет. После того, как пользователь ответит на вопрос, в сообщении выводим, сколько времени ему потребовалось на ответ.

Для решения поставленной задачи в модуле формы написан следующий код:

&НаКлиенте
Асинх Процедура ЗадатьВопрос(Команда)
    
    ВремяНачала = ТекущаяДата();
    Результат = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет);
    Сообщить("Начало загрузки");
    ЗатраченноеВремя = ТекущаяДата() - ВремяНачала;
    ТекстСообщения = СтрШаблон("Затраченное время: %1 %2", ЗатраченноеВремя, "с");
    Сообщить(ТекстСообщения);
    
КонецПроцедуры

В коде используется асинхронная функция платформы ВопросАсинх, которая возвращает Обещание. Это Обещание указывается в качестве параметра оператора Ждать. Система ожидает реакции пользователя. Когда пользователь ответит на вопрос и Обещание “разрешится”, в переменную Результат будет помещен выбранный пользователем вариант ответа на вопрос (Да или Нет).

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

Копирование файла

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

&НаКлиенте
Асинх Процедура СкопироватьФайл(Команда)
    
    Попытка
        Результат = Ждать КопироватьФайлАсинх("C:\1.xlsx", "D:\1.xlsx");
    Исключение
        Сообщить(ОписаниеОшибки());
    КонецПопытки;
    
КонецПроцедуры

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

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

Проверка возможности работы с внешней системой

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

Напомним, что выгрузку можно производить только в том случае, когда в константе Лицензия заполнены данные лицензии для выгрузки. Если лицензия не заполнена, нужно предложить пользователю ввести данные лицензии, сохранить их в константе, после чего открыть форму для выгрузки данных во внешнюю систему.

Программный код выглядит следующим образом:

&НаКлиенте
Асинх Процедура ОтправитьВоВнешнююСистему(Команда)
    
    ДанныеДляВнешнейСистемы = Новый Структура;
    ДанныеДляВнешнейСистемы.Вставить("Дата",        ТекущаяДата());
    ДанныеДляВнешнейСистемы.Вставить("Описание",    "ОписаниеПлатежа");
    //...
    
    Если ПроверитьЛицензию() Тогда
        //Открыть форму подключения к внешней системе
    Иначе
        
        ТекстВопроса = "Для подключения к внешней системе необходимо указать данные лицензии.
            |Ввести сейчас?";
        Ответ = Ждать ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
        Если Ответ = КодВозвратаДиалога.Да Тогда
            
            Лицензия = "";
            Лицензия = Ждать ВвестиСтрокуАсинх(Лицензия, "Лицензия");
            
            Если НЕ ПустаяСтрока(Лицензия) Тогда
                СохранитьЛицензию(Лицензия);
                //Открыть форму подключения к внешней системе                
            КонецЕсли;
            
        КонецЕсли;
    
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Функция ПроверитьЛицензию()

    Лицензия = Константы.Лицензия.Получить();
    Возврат ЗначениеЗаполнено(Лицензия);

КонецФункции

&НаСервереБезКонтекста
Процедура СохранитьЛицензию(Лицензия)

    Константы.Лицензия.Установить(Лицензия);

КонецПроцедуры

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

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

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

Вопрос пользователю во время закрытия формы

Перед закрытием формы или перед закрытием всего приложения нужно задать вопрос, действительно ли следует выполнить закрытие, чтобы пользователь не потерял несохраненные данные. Попробуем решить эту задачу с использованием асинхронных методов. В форме создадим обработчик события ПередЗакрытием:

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

Начиная с платформы 8.3.8, нужно разделять два варианта закрытия:

  • Форма закрывается при закрытии приложения в целом
  • Пользователь закрывает конкретную форму

Поэтому в коде обрабатывается два этих варианта. При закрытии приложения в целом параметр ЗавершениеРаботы принимает значение Истина. Важно, что программно нельзя отменить закрытие приложения, можно только показать предупреждение пользователю. Для этого параметр Отказ устанавливается в значение Истина. В таком случае пользователю при закрытии будет показано предупреждение, которое передается в параметре ТекстПредупреждения.

При закрытии конкретной формы пользователю задается вопрос. Если пользователь отвечает, что не требуется закрывать форму, параметр Отказ устанавливается в значение Истина, форма не закрывается.

Но при проверке работоспособности этого кода в режиме “1С:Предприятие” обнаруживается неожиданное поведение. При закрытии приложения никакое предупреждение не отображается. При закрытии формы сначала форма закрывается, а только потом отображается вопрос. При любом ответе пользователя на вопрос ничего не происходит, потому что форма уже закрылась.

Почему система ведет себя таким образом?

При закрытии приложения в целом или конкретной формы платформа вызывает обработчик события формы ПередЗакрытием. В этом обработчике можно управлять поведением системы при помощи изменения значения параметров. В приведенном выше примере изменялись значения параметров Отказ и ТекстПредупреждения.

Вспомним, что в процедурах и функциях с модификатором Асинх параметры всегда передаются по значению. Поэтому изменения значений параметров, произведенные внутри асинхронной процедуры ПередЗакрытием, недоступны для кода, который вызвал эту процедуру. Поэтому “снаружи” этой процедуры параметр Отказ принимает значение Ложь. В таком случае при закрытии приложения предупреждение пользователю не выводится, приложение просто закрывается. Это и наблюдается в пользовательском режиме именно потому, что процедура ПередЗакрытием асинхронная:

Процедура ПередЗакрытием

Когда закрывается только одна форма, а не приложение в целом, в приведенном коде происходит вызов асинхронной функции ВопросАсинх. При этом выполнение процедуры ПередЗакрытием приостанавливается, управление возвращается к коду, вызвавшему этот метод. Поскольку процедуру ПередЗакрытием вызвала платформа, управление возвращается платформе. Параметр Отказ в коде не был изменен, кроме того любые изменения параметра в самой асинхронной процедуре “не видны” тому коду, который вызвал эту асинхронную процедуру, потому что параметры в нее передаются по значению. Значит, при возвращении управления платформе параметр Отказ принимает значение Ложь, поэтому форма закрывается. Вопрос пользователю отображается, но когда пользователь ответит на вопрос, форма уже закрыта, отменить ее закрытие невозможно.

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

Вместо асинхронной функции в данном случае можно использовать метод обратного вызова:

&НаКлиенте
Перем ЗакрытиеФормыРазрешено;

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

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

Ошибки при использовании асинхронных функций в коде

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

Ошибка 1 – Только клиентский контекст

Объявим асинхронную функцию с директивой компиляции НаСервере:

&НаСервере
Асинх Функция ПроверитьНаСервере()

    //...
    Возврат РезультатПроверки;

КонецФункции

При проверке синтаксиса модуля конфигуратор выдаст ошибку:

Недопустимое объявление асинхронной процедуры или функции

Ошибка возникла, потому что модификатор Асинх можно использовать только для клиентских процедур и функций. К тому же тип данных Обещание доступен только на клиенте. Поэтому для исправления ошибки нужно или убрать из описания функции модификатор Асинх, или использовать директиву компиляции НаКлиенте.

Ошибка 2 – Оператор Ждать только в асинхронных методах

Рассмотрим следующий пример. В клиентской процедуре задаем вопрос пользователю:

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

При проверке синтаксиса модуля конфигуратор выдает ошибку:

Оператор Ждать (Await) может употребляться только в асинхронных процедурах или функциях

Для исправления ошибки нужно воспользоваться модификатором Асинх для описания этой процедуры:

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

Ошибка 3 – Подходящие версии платформы

Еще один пример. Процедура выглядит синтаксически корректно:

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

Но проверка синтаксиса в конфигураторе выдает ошибки:

Ожидается определение процедуры/функции

<<?>>Асинх Процедура ЗадатьВопрос(Команда) (Проверка: Тонкий клиент)

Ожидается символ ';'

    Результат = Ждать<<?>> ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет); (Проверка: Тонкий клиент)

Такая ошибка может возникать, если открыть данный программный код на платформе 8.3.17 или предыдущих, в которых еще не реализован функционал Обещаний. Обратите внимание, что в приведенном фрагменте кода не подсвечиваются красным ключевые слова Асинх и Ждать. Чтобы ошибка не возникала, нужно использовать платформу 8.3.18 или новее.

Принципы работы асинхронных функций

Разберем подробнее, как работают асинхронные функции, возвращающие значение типа Обещание.

Ранее отмечалось, что оператор Ждать выполняет ожидание завершения работы вызванной асинхронной функции, которая вернула Обещание, переданное как параметр оператора Ждать. Получается, что оператор Ждать блокирует поток выполнения команд, пока асинхронная функция не отработает? Как тогда такие функции могут использоваться в веб-клиенте?

Нет, блокировки потока не происходит. Асинхронность в платформе “1С:Предприятие” осталась такая же, как и при работе с методами обратного вызова. Изменился только подход к написанию кода.

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

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

На примере продемонстрируем, что при выполнении оператора Ждать действительно происходит возврат к вызвавшему методу:

&НаКлиенте
Асинх Процедура ВывестиОтветНаВопрос(Команда)
    Обещание = ОтветНаВопрос();
    Сообщить("Пользователь выбрал вариант");
    Сообщить(Ждать Обещание);
КонецПроцедуры

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

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

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

&НаКлиенте
Асинх Процедура СкопироватьФайл(Команда)
    
    СкопированныйФайл = Ждать КопироватьФайлАсинх("C:\1.xlsx", "D:\1.xlsx");
    Сообщить(СкопированныйФайл);
    
КонецПроцедуры

В данном фрагменте кода приостановка выполнения происходит в операторе Ждать. Приостановка будет продолжаться до того момента, пока не разрешится Обещание, возвращаемое асинхронной функцией КопироватьФайлАсинх. Другими словами – до тех пор, пока файл не будет скопирован либо не возникнет исключение при выполнении функции. После разрешения Обещания происходит возобновление выполнения процедуры. Имя файла-получателя будет присвоено переменной СкопированныйФайл. После этого платформа может переходить к выполнению следующей строки кода, сообщить значение этой переменной пользователю.

Такое поведение платформы можно сравнить с использованием метода обратного вызова и провести аналогию. Код уже знаком нам:

&НаКлиенте
Процедура СкопироватьФайл(Команда)
    
    ОписаниеОповещения = Новый ОписаниеОповещения("КопированиеФайлаЗавершение", ЭтотОбъект);
    НачатьКопированиеФайла(ОписаниеОповещения, "C:\1.xlsx", "D:\1.xlsx");
    
КонецПроцедуры

&НаКлиенте
Процедура КопированиеФайлаЗавершение(СкопированныйФайл, ДополнительныеПараметры) Экспорт
    
    Сообщить(СкопированныйФайл);

КонецПроцедуры

Здесь аналог приостановки – это завершение процедуры СкопироватьФайл после вызова метода НачатьКопированиеФайла. Аналог возобновления – это выполнение метода обратного вызова, указанного в описании оповещения, после завершения копирования файла. Отличие заключается в форме – вместо последовательного кода используется набор логически связанных процедур.

Поэтому ранее отмечалось, что глобально смысл асинхронности в платформе не изменился с появлением Обещаний. Изменился только способ написания кода.

Рассмотрим более сложный пример, демонстрирующий приостановку и возобновление программного кода. На форме есть реквизит с типом ТаблицаЗначений, у которой есть две колонки – ИмяФайла и Размер:

ТаблицаЗначений

Необходимо найти в определенном каталоге все файлы с расширением xlsx и вывести в эту таблицу значений на форме имя и размер найденных файлов:

Сведения о файлах

Программный код для решения поставленной задачи приведен ниже:

 1 &НаКлиенте
 2 Асинх Процедура ВывестиСведенияОФайлах(Команда)
 3     ОбещаниеФайлы = ПолучитьСведенияОФайлах();
 4     МассивСведений = Ждать ОбещаниеФайлы;
 5     ТаблицаФайлов.Очистить();
 6     Для каждого СведенияОФайле Из МассивСведений Цикл
 7         НоваяСтрока = ТаблицаФайлов.Добавить();
 8         ЗаполнитьЗначенияСвойств(НоваяСтрока, СведенияОФайле);
 9     КонецЦикла;
10 КонецПроцедуры
11 
12 &НаКлиенте
13 Асинх Функция ПолучитьСведенияОФайлах()
14     Результат = Новый Массив;
15     ОбещаниеНайденныеФайлы = НайтиФайлыАсинх("C:\Users\User1\Downloads\", "*.xlsx", Ложь);
16     НайденныеФайлы = Ждать ОбещаниеНайденныеФайлы;
17     Для каждого НайденныйФайл Из НайденныеФайлы Цикл
18         ОбещаниеРазмерФайла = НайденныйФайл.РазмерАсинх();
19         РазмерФайла = Ждать ОбещаниеРазмерФайла;
20         Результат.Добавить(Новый Структура("ИмяФайла, Размер", НайденныйФайл.Имя, РазмерФайла));
21     КонецЦикла;
22     Возврат Результат;
23 КонецФункции

Разберем, в каком порядке будут выполняться строки этого асинхронного кода. Строки кода пронумерованы, чтобы удобнее было ссылаться на них.

  1. При нажатии на кнопку на форме срабатывает обработчик – процедура ВывестиСведенияОФайлах (строка 2). В строке 3 вызывается асинхронная функция ПолучитьСведенияОФайлах (строка 13).
  2. В строке 14 создается массив, в который будем помещать данные о найденных файлах. Функция ПолучитьСведенияОФайлах возвращает массив структур, каждая структура содержит имя и размер одного найденного файла.
  3. В строке 15 производится вызов асинхронной функции платформы НайтиФайлыАсинх. Эта функция возвращает Обещание, которое в строке 16 используется в качестве параметра оператора Ждать. Выполнение функции ПолучитьСведенияОФайлах приостанавливается, управление возвращается к вызвавшему эту функцию коду – в строку 3.
  4. Асинхронная функция ПолучитьСведенияОФайлах возвращает Обещание (строка 3). Это Обещание в строке 4 передается в качестве параметра оператора Ждать. Выполнение процедуры ВывестиСведенияОФайлах приостанавливается.
  5. Получается, что в данный момент существует 2 Обещания (ОбещаниеФайлы и ОбещаниеНайденныеФайлы), которые предназначены для хранения еще неизвестных результатов функций ПолучитьСведенияОФайлах и НайтиФайлыАсинх соответственно. Эти функции еще не завершили выполнение, поэтому невозможно получить их результат, нужно дождаться их выполнения. При этом важно, что система не может ждать выполнения обещаний нескольких асинхронных методов одновременно. Это может происходить только последовательно.
  6. После завершения работы функции НайтиФайлыАсинх происходит возобновление выполнения функции ПолучитьСведенияОФайлах в строке 16, восстанавливаются сохраненные на момент приостановки значения локальных переменных. В переменную НайденныеФайлы помещается массив объектов типа Файл. Остается только одно нерешенное Обещание – ОбещаниеФайлы.
  7. Выполнение кода перемещается на строку 17, в цикле перебираются элементы массива НайденныеФайлы.
  8. В строке 18 для каждого найденного файла вызывается асинхронная функция РазмерАсинх, которая возвращает еще одно Обещание – ОбещаниеРазмерФайла. В данный момент существует 2 Обещания (ОбещаниеФайлы и ОбещаниеРазмерФайла).
  9. В строке 19 используется оператор Ждать, поэтому еще раз происходит приостановка выполнения функции ПолучитьСведенияОФайлах. Получается, что никакой код в этот момент не выполняется, стек вызовов пустой, вернуть управление вызывающему коду нет возможности, поэтому управление возвращается платформе.
  10. После того, как размер файла определен, выполнение функции ПолучитьСведенияОФайлах возобновляется в строке 19. В переменной РазмерФайла содержится вычисленный размер файла, который был “спрятан” в ОбещаниеРазмерФайла.
  11. В строке 20 в массив Результат добавляется структура с именем и размером найденного файла. Строки 17-21 выполняются столько раз, сколько файлов было найдено, т.е. сколько элементов есть в массиве НайденныеФайлы.
  12. В строке 22 происходит возврат из функции ПолучитьСведенияОФайлах. Значит, ожидание в строке 4 завершается, в переменной МассивСведений содержится массив структур с информацией о найденных файлах.
  13. Остается только вывести полученные сведения о найденных файлах на экран, что происходит в строках 5-9.
  14. Выполнение процедуры ВывестиСведенияОФайлах завершается в строке 10.

Итак, вот в таком порядке выполняются строки кода, который выглядит последовательным, но фактически при его выполнении происходит приостановка и возобновление асинхронных методов.

Изобразим на схеме указанную последовательность шагов:

Последовательность шагов

Особенности использования отладчика при работе с асинхронными функциями

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

Для удобства разработчиков в отладчике была реализована “имитация” синхронного выполнения асинхронных функций. Это означает, что при выполнении команд Шагнуть через (F10) и Шагнуть в (F11) на строке, содержащей оператор Ждать, отладчик будет дожидаться завершения работы асинхронной функции, после чего перейдет к следующей строке кода. Хотя фактически при этом произойдет выход из текущего метода и передача управления вызвавшему эту функцию коду. За счет такого поведения “эмулируется” синхронная работа отладчика.

Рассмотрим пример. Есть следующий асинхронный код:

 1 &НаКлиенте
 2 Асинх Процедура ВывестиОтветНаВопрос(Команда)
 3     Обещание = ОтветНаВопрос();
 4     Сообщить("Пользователь выбрал вариант");
 5     Сообщить(Ждать Обещание);
 6 КонецПроцедуры
 7 
 8 &НаКлиенте
 9 Асинх Функция ОтветНаВопрос()
10     Возврат Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет);
11 КонецФункции

Разработчик в отладчике остановился на строке 10, в которой используется оператор Ждать. При выполнении команды Шагнуть через (F10) в пользовательском режиме будет показан вопрос, а также выведено сообщение из строки 4. После ответа пользователя на вопрос указатель текущей строки кода перемещается на строку 11, хотя фактически строка 4 уже была выполнена.

Еще раз выполним команду Шагнуть через (F10). При этом указатель текущей строки окажется на строке 6, а не на строке 4, как это произошло бы при синхронных вызовах. Получается, что отладчик может оказаться не на той строке, на которой разработчик ожидает остановиться.

Такое поведение объясняется природой асинхронных вызовов, потому что при ожидании на строке 10 выполнение функции ОтветНаВопрос приостановливается, управление передается вызывающем коду в процедуру ВывестиОтветНаВопрос. Выводится сообщение в строке 4. В строке 5 используется оператор Ждать, поэтому происходит приостановка выполнения процедуры ВывестиОтветНаВопрос, управление возвращается платформе.

После того, как пользователь ответит на вопрос, выполнение функции ОтветНаВопрос возобновляется, происходит возврат из этой функции. Ожидание в строке 5 завершается, выводится сообщение с выбранным пользователем вариантом.

Поэтому отладчик показывает, что при выполнении шага со строки 11 указатель текущей строки оказывается на строке 6.

Чтобы разработчику было проще разобраться с порядком вызовов асинхронных функций, в инструменте Стек вызовов цветом выделяются строки. После возобновления асинхронной функции (текущая строка – 11) черным цветом будет выделен стек, выполняющийся в данный момент, а серым – стек, из которого была вызвана асинхронная функция:

Стек вызовов

Может сложиться ситуация, что исходная процедура, которая вызвала асинхронную функцию, к моменту возобновления работы функции уже завершила свое выполнение. Рассмотрим пример:

 1 &НаКлиенте
 2 Асинх Процедура ВывестиОтветНаВопрос(Команда)
 3     ОтветНаВопрос();
 4 КонецПроцедуры
 5 
 6 &НаКлиенте
 7 Асинх Функция ОтветНаВопрос()
 8     Ответ = Ждать ВопросАсинх("Выполнить загрузку?", РежимДиалогаВопрос.ДаНет);
 9     Сообщить("Пользователь выбрал вариант");
10     Сообщить(Ответ);
11 КонецФункции

Если в отладчике выполнить остановку на строке 11 и сделать шаг (нажатием F10 или F11), то произойдет выход из отладки, в строку 4 выполнение кода не попадет. Почему? Дело в том, что в строке 8 используется оператор Ждать, поэтому происходит приостановка выполнения функции ОтветНаВопрос, управление возвращается к вызывающему коду – процедуре ВывестиОтветНаВопрос. В строке 4 происходит завершение работы процедуры.

Когда пользователь ответит на вопрос, произойдет возобновление выполнения функции в строке 8, будут выведены сообщения из строк 9 и 10. При выходе из функции ОтветНаВопрос в строке 11 возвращаться уже некуда, поскольку вызвавший эту функцию код уже отработал. Но тем не менее в стеке вызовов этот код будет отображаться серым цветом:

Стек вызовов

Использование фоновых заданий для выполнения длительных операций

Ранее отмечалось, что асинхронность в платформе “1С:Предприятие” появилась из-за требований, предъявляемых браузерами. Например, существуют ограничения на время выполнения скриптов, чтобы предотвратить блокировку интерфейса браузера.

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

Для примера рассмотрим типовую конфигурацию “Бухгалтерия предприятия”. В документе СчетФактураВыданный есть форма ФормаДокументаКорректировочнаяСправка. В этой форме реализован обработчик команды Заполнить. Поскольку заполнение документа может занять длительное время, эта операция выполняется с помощью фонового задания.

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

Запускается фоновое задание, исполняющее этот код. Отображается общая форма с анимированной картинкой:

Форма с анимацией

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

Для доступа из формы к результату заполнения документа, помещенному в фоновом задании во временное хранилище, используется реквизит формы АдресХранилища. Чтобы из клиентского приложения можно было проверить состояние запущенного фонового задания (завершено успешно, еще выполняется или завершено с ошибкой), его идентификатор также хранится в реквизите формы  ИдентификаторЗадания:

Форма ИдентификаторЗадания

Приведем фрагменты программного кода модуля формы, которые имеют отношение к фоновому заполнению документа. При помощи комментариев отметим назначение отдельных процедур или переменных.

//Клиентские переменные для управления формой длительной операции
&НаКлиенте
Перем ПараметрыОбработчикаОжидания;

&НаКлиенте
Перем ФормаДлительнойОперации;

//Обработчик команды Заполнить
&НаКлиенте
Процедура Заполнить(Команда)
    
    Если Объект.ДокументыОснования.Количество() <> 0 Тогда
        Если Объект.Проведен Тогда
            ТекстВопроса = НСтр("ru = 'Перед заполнением проведение документа будет отменено, а список документов-основанией будет очищен. Заполнить?'");
        Иначе
            ТекстВопроса = НСтр("ru = 'Список документов-основанией будет очищен. Заполнить?'");
        КонецЕсли;
        Оповещение = Новый ОписаниеОповещения("ВопросЗаполнитьДокументЗавершение", ЭтотОбъект);
        ПоказатьВопрос(Оповещение, ТекстВопроса, РежимДиалогаВопрос.ДаНет);
    Иначе
        ЗаполнитьДокумент();
    КонецЕсли;
    
КонецПроцедуры

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

//Подключение обработчика ожидания для проверки выполнения фонового задания
&НаКлиенте
Процедура ЗаполнитьДокумент()

    Результат = ЗаполнитьДокументНаСервере();

    Если Результат.ЗаданиеВыполнено Тогда
        ОповеститьОбИзменении(Объект.Ссылка);
    Иначе
        ИдентификаторЗадания = Результат.ИдентификаторЗадания;
        АдресХранилища       = Результат.АдресХранилища;
        
        ДлительныеОперацииКлиент.ИнициализироватьПараметрыОбработчикаОжидания(ПараметрыОбработчикаОжидания);
        ПодключитьОбработчикОжидания("Подключаемый_ПроверитьВыполнениеЗадания", 1, Истина);
        ФормаДлительнойОперации = ДлительныеОперацииКлиент.ОткрытьФормуДлительнойОперации(ЭтаФорма, ИдентификаторЗадания);
    КонецЕсли;
    
КонецПроцедуры

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

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

    Иначе
        
        НаименованиеЗадания = НСтр("ru = 'Заполнение документа ""Корректировочная справка по розничным продажам""'");
        
        Результат = ДлительныеОперации.ЗапуститьВыполнениеВФоне(
            УникальныйИдентификатор,
            "Документы.СчетФактураВыданный.ПодготовитьДанныеДляЗаполненияКорректировочнойСправки",
            СтруктураПараметров,
            НаименованиеЗадания);

        АдресХранилища = Результат.АдресХранилища;
    КонецЕсли;

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

    Возврат Результат;

КонецФункции

//Проверка выполнения задания по его идентификатору
&НаСервереБезКонтекста
Функция ЗаданиеВыполнено(ИдентификаторЗадания)
    
    Возврат ДлительныеОперации.ЗаданиеВыполнено(ИдентификаторЗадания);
    
КонецФункции

//Проверка, нужно ли закрыть форму длительной операции. Установка интервала ожидания
&НаКлиенте
Процедура Подключаемый_ПроверитьВыполнениеЗадания()

    Попытка
        Если ФормаДлительнойОперации.Открыта() 
            И ФормаДлительнойОперации.ИдентификаторЗадания = ИдентификаторЗадания Тогда
            Если ЗаданиеВыполнено(ИдентификаторЗадания) Тогда
                ЗагрузитьПодготовленныеДанные();
                ДлительныеОперацииКлиент.ЗакрытьФормуДлительнойОперации(ФормаДлительнойОперации);
            Иначе
                ДлительныеОперацииКлиент.ОбновитьПараметрыОбработчикаОжидания(ПараметрыОбработчикаОжидания);
                ПодключитьОбработчикОжидания(
                    "Подключаемый_ПроверитьВыполнениеЗадания",
                    ПараметрыОбработчикаОжидания.ТекущийИнтервал,
                    Истина);
            КонецЕсли;
        КонецЕсли;
    Исключение
        ДлительныеОперацииКлиент.ЗакрытьФормуДлительнойОперации(ФормаДлительнойОперации);
        ВызватьИсключение;
    КонецПопытки;

КонецПроцедуры

//Получение данных из временного хранилища, загрузка в табличную часть документа
&НаСервере
Процедура ЗагрузитьПодготовленныеДанные()
    
    СтруктураДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
    Если ТипЗнч(СтруктураДанных) <> Тип("Структура") Тогда
        Возврат;
    КонецЕсли;
    
    Если СтруктураДанных.Свойство("ДокументыОснования") Тогда
        Объект.ДокументыОснования.Загрузить(СтруктураДанных.ДокументыОснования);
    КонецЕсли;
    
    //работа с формой...
    
    Модифицированность = Истина;
    
КонецПроцедуры

Подобный код, выполняющий длительные операции в фоновом задании, можно встретить во всех конфигурациях на базе БСП – Управление торговлей 11, Комплексная автоматизация 2, Зарплата и управление персоналом 3 и другие.

Также в демонстрационной базе БСП можно ознакомиться, как работает этот механизм. Для демонстрации возможностей подсистемы в демо-базе реализована обработка _ДемоДлительнаяОперация.

Обратите внимание, что данный программный код значительно отличается от рассмотренного ранее асинхронного кода.

В коде не используются асинхронные процедуры, имя которых начинается с Показать или Начать, также нет функций с модификатором Асинх.

Запуск фонового задания производится на сервере, в то время, как асинхронные методы, разбираемые в предыдущих главах, являются клиентскими. Вообще работа с фоновыми заданиями производится только на сервере. Например, проверка того, выполнено ли задание, выполняется в серверной функции.

Для работы фонового задания создается отдельный сеанс, в котором и выполняется длительная операция. При вызове асинхронных методов отдельный сеанс не создается, все действия выполняются в одном сеансе. В целом асинхронность в платформе не является реализацией многопоточности.

При вызове асинхронной процедуры в описании оповещения указывается, какую процедуру необходимо выполнить при завершении работы асинхронного метода. При использовании оператора Ждать происходит приостановка выполнения метода, после завершения работы асинхронной функции выполняется возобновление приостановленного метода. Таким образом, из программного кода не нужно проверять, отработал ли уже асинхронный метод. Платформа самостоятельно выполнит нужное действие – передаст управление методу обратного вызова или возобновит приостановленный метод.

При выполнении длительных действий в фоновом задании нет возможности получить “уведомление” от сервера, что фоновое задание завершено, чтобы получить подготовленные данные и загрузить их в табличную часть документа. Поэтому приходится подключать обработчик ожидания, который с заданной периодичностью проверяет состояние фонового задания с определенным идентификатором. Как только такая проверка определяет, что фоновое задание завершилось, можно приступать к обработке данных, помещенных во временное хранилище в фоновом задании.

Таким образом, длительные операции с использованием фоновых заданий и асинхронные методы – это принципиально разные механизмы. Правда на практике достаточно часто термин “асинхронное выполнение” применяется и для способа с фоновыми заданиями, поскольку при этом не блокируется интерфейс приложения. Поэтому обязательно нужно уточнять, какая именно асинхронность имеется в виду.

Заключение

Итак, в данной статье продемонстрировано, что современные браузеры диктуют требования, из-за которых фирме “1С” пришлось добавить асинхронные методы в платформу “1С:Предприятие”.

Отличие асинхронного метода от синхронного заключается в том, что асинхронный метод не блокирует основной поток выполнения кода. После вызова асинхронного метода управление вернется к коду, вызвавшему этот метод, до того, как сам асинхронный метод завершит свою работу.

Существует 2 варианта асинхронных методов – с использованием методов обратного вызова и с использованием нового типа данных Обещание и оператора Ждать, которые появились в платформе 8.3.18.

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

Поэтому фирма “1С” упростила работу разработчикам, добавив в платформу 8.3.18 асинхронные функции, возвращающие Обещания. Использование таких методов делает код более понятным, простым для анализа и модификации – похожим на код с использованием аналогичных синхронных методов.

В феврале 2021 года использование платформы 8.3.18 для работы с типовыми конфигурациями не является обязательным. Поэтому новые асинхронные функции, возвращающие Обещания, еще нельзя встретить в коде. Ожидается, что в скором времени этот функционал будет использоваться повсеместно – начиная с БСП, откуда он попадет во все типовые конфигурации, базирующихся на БСП.

Сейчас как раз есть время подготовиться, изучить новый функционал, разобраться с асинхронностью в платформе “1С:Предприятие”. Когда в БСП и в типовых начнут применяться асинхронные функции – это не будет неожиданностью. К тому же с кодом станет работать удобнее, проще, разработчики смогут сконцентрироваться на реализации алгоритмов, а не на преодолении сложностей встроенного языка.

База с приведенными в статье фрагментами кода

Хотите еще?

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

33 минуты, рекомендуем к просмотру :)

Не пропустите новый курс (по специальной цене для первого потока)!

Коллеги, запускаем новый курс по EDT и Git, первый курс в серии курсов по DevOps.

Если Вы тимлид / руководитель отдела разработки / работаете в команде из 5+ человек над одним проектом / планируете перейти в ведущие франчайзи не простым разработчиком – рекомендуем внимательно его посмотреть:

Комментарии / обсуждение (32):

  1. Т А

    Добрый день, коллеги. Подскажите, пожалуйста, как, применяя рассмотренные новшества, решить следующий вопрос:
    При записи документа из формы, пользователю требуется задать вопрос. В зависимости от ответа (ДА/НЕТ) – создается/не создается запись в регистре сведений.
    Проблема в том, что модальный метод Вопрос() я не хочу использовать, а немодальный метод ПоказатьВопрос() – не ждет, и получает ответ тогда, когда запись документа уже прошла! А мне нужно чтобы система дождалась ответ на клиенте, чтобы передать его дальше на сервер, и там делать/не делать запись в регистр.
    Можете привести пример кода, чтобы решить такую задачу с помощью вышеописанных новшеств платформы?
    Благодарю за внимание.

    • Василий Ханевич

      Добрый день!
      На сайте ИТС есть рекомендации фирмы “1С” по использованию вопросов в обработчике формы ПередЗаписью.
      Красивого решения нет, можно порекомендовать создавать собственные команды вместо типовых, тогда в них можно будет реализовать свой алгоритм, задавать вопрос, в зависимости от ответа выполнять запись в базу.
      За образец можно взять код из типовой Бухгалтерии предприятия (форма элемента справочника Контрагенты). Для командной панели формы отключено автозаполнение, создана своя команда “Записать и закрыть”.

  2. tim

    Большое спасибо за такую обстоятельную статью!

    Но я никак не могу понять однин момента: зачем помечать собственные асинхронные функции ключевым словом Асинх? Почему я не могу написать Ждать МояФункция() и не помечать МояФункция() как Асинх? Почему это нужно именно разработчику прикладного кода это писать, почему транслятор кода не может сам понять это во время трансляции?

    • Василий Ханевич

      Добрый день!
      Предполагаю, что это нужно платформе, чтобы разделить синхронно и асинхронно выполняемый код, создавать “под капотом” необходимые структуры и данные для выполнения кода. И не анализировать сам программный код, а только сигнатуру метода.
      Хотя согласен, это можно было бы сделать и на уровне платформы, не перекладывая на разработчика.

  3. magic1s

    Или: зачем нужно ОписаниеОповещения?
    Почему бы вместо
    ПоказатьВопрос(Новый ОписаниеОповещения(“ПоказатьВопросЗавершение”, ЭтотОбъект), “Выполнить загрузку?”, РежимДиалогаВопрос.ДаНет);
    Не писать
    ПоказатьВопрос(“ПоказатьВопросЗавершение”, “Выполнить загрузку?”, РежимДиалогаВопрос.ДаНет);
    – без всяких “Новый ОписаниеОповещения”?

    • Василий Ханевич

      Добрый день!
      Я не разработчик платформы, поэтому могу только предположить.
      Для описания оповещения нужно указать имя процедуры, модуль, где эта процедура находится, дополнительные данные, которые будут переданы в обработчик.
      Поэтому было решено сделать новый объект, который включает в себя все эти составные части. И в процедурах в качестве параметра указывать один объект, а не несколько параметров.

    • tim

      Потому что просто имени функции не достаточно в большинстве случаев, нужно больше контекста, в том числе возможность передать ДопПараметры дальше. Есть же еще не только обработка успешного выполнения, но и обработка ошибок выполнения асинхронных вызовов. Поэтому решили все это обернуть в один объект.

      • Василий Ханевич

        Согласен, я в целом так же и ответил выше.
        Спасибо за дополнения!

  4. magic1s

    “Поэтому рекомендуется использовать блокирующие окна вместо модальных. …
    Для пользователя внешне все выглядит так же, как и с использованием модальных окон. Но разработчикам на платформе “1С:Предприятие” пришлось переработать программный код с использованием новых неблокирующих методов, чтобы конфигурация корректно работала в веб-клиенте.”
    И чтоб разработчикам платформы было не сделать, чтобы старые методы стали “неблокирующими”? Зачем с старым добавлять новые?

    • Василий Ханевич

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

  5. Андрей

    Здравствуйте. А вы проверяли асинхронность этого кода?
    &НаКлиенте
    Асинх Процедура ВывестиДанныеИзВнешнейСистемы(Команда)

    Результат = Ждать ПолучитьЗначениеИзВнешнейСистемы();

    КонецПроцедуры

    &НаКлиенте
    Асинх Функция ПолучитьЗначениеИзВнешнейСистемы()

    //Здесь должен располагаться программный код для получения данных…
    Возврат Значение;

    КонецФункции

    У меня все происходит синхронно. Я сделал вывод, что сделать свою собственную асинх не получится. Только с помощью встроенных асинх методов.

    • Василий Ханевич

      Добрый день!
      Да, Вы правильно заметили.
      Этот механизм предназначен для работы с асинхронными функциями, реализованными на уровне платформы (например, ВопросАсинх, КопироватьФайлАсинх и т.д.).
      Синтаксис языка позволяет определять собственные асинхронные функции, но особого смысла в них нет.

  6. Олег

    А есть ли возможность ожидать несколько обещаний сразу, в вашей статье файлы последовательно загружаются в цикле один за одним.
    В развитых языка есть такие конструкции
    TypeScript
    Promise.All(…array_of_promises).Then(results => …)
    либо C#

    Task.WhenAll(new []{Task.Run(()=>Task.Delay(1000)), Task.Run(()=>1)})

  7. Екатерина

    Есть ограничения в мобильной платформе по количеству одновременного использования асинхронных процедур и функций?

  8. kservice

    Добрый день!

    Заявлено, что: “В платформе 8.3.21 добавлена возможность асинхронной отправки HTTP-запросов на веб-клиенте”.

    Используется платформа 8.3.22.1709, у конфигурации свойство “Режим совместимости” установлен в “8.3.21”.
    Веб-клиент опубликован на сервере с использованием “Apache 2.4”.
    При исполнении кода в клиентском модуле метода “ОтправитьДляОбработкиАсинх()” объекта “HTTPСоединение” возникает ошибка: “Ошибка при вызове метода контекста”.

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

    Как сделать так, чтобы http-запросы работали на веб-клиенте так как это заявлено в платформе?

    • Василий Ханевич

      Добрый день!
      Не сталкивался с такой ошибкой. Поэтому могу дать только общие рекомендации – посмотреть, какие конкретно подробности сохраняются при возникновении ошибки в журнал регистрации, задать этот вопрос разработчикам платформы на v8@1c.ru.

  9. Павел

    Нечетко разделяете асинх процедуры/функции с обычными процедурами/функциями, поэтому понимание Обещание/Асинх/Ждать немного теряется, например:

    Процедура НачатьСмену(Команда)

    ТекстВопроса = “Вы действительно хотите начать смену?”;
    // Возьмем обещание от пользователя, что он выберет ответ.
    ОбещаниеОтветаОтПользователя = ВопросАсинх(ТекстВопроса, РежимДиалогаВопрос.ДаНет);
    // Занимаемся своими делами
    Сообщить(“ДелаемДела”);
    // Завершив наши дела, пора вспомнить про пользователя.
    НачатьЖдатьОтветОтПользователя(ОбещаниеОтветаОтПользователя);
    // Ниже код выполнится не дожидаясь пользователя.
    Сообщить(“КонецКоманды”);

    КонецПроцедуры

    &НаКлиенте
    Асинх Функция НачатьЖдатьОтветОтПользователя(ОбещаниеОтветаОтПользователя)

    Ответ = Ждать ОбещаниеОтветаОтПользователя;
    // Далее код в этой процедуре будет выполнятся только после ответа пользователя.
    Сообщить(Ответ);

    КонецФункции // ЗадатьВопросПользователю()

    • Василий Ханевич

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

      • Павел

        &НаКлиенте
        Асинх Процедура НачатьСмену(Команда)

        ТекстВопроса = “Вы действительно хотите начать смену?”;
        РезультатВопроса = Ждать ВопросАсинх(ТекстВопроса , РежимДиалогаВопрос.ДаНет);
        Сообщить(“ДелаемДела”);
        Сообщить(Ответ);
        Сообщить(“КонецКоманды”);

        КонецПроцедуры

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

        • Василий Ханевич

          Да, конечно, отличия есть. Можно получить Обещание, выполнить другие шаги, затем выполнить Ждать Обещание. И можно Обещание передать в качестве параметра в другой метод.
          Согласен.

  10. Борис

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

    Вопрос очень простой, как в событии таблицы формы (в которой динамический список) установить отказ и предупредить об этом пользователя:

    &НаКлиенте
    Асинх Процедура СписокПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, Группа, Параметр)

    Отказ = Истина;
    ПредупреждениеАсинх(“Текст”)

    КонецПроцедуры

    То же самое и со “СтандартнаяОбработка = Ложь” – устанавливаем модификатор процедуры “Асинх” и “СтандартнаяОбработка = Ложь” перестает работать:)

    • Василий Ханевич

      Добрый день!
      Вы столкнулись с особенностью асинхронных обработчиков. В данной статье она рассмотрена на примере обработчика события формы ПередЗакрытием (раздел Вопрос пользователю во время закрытия формы).
      Синтаксически обработчик событий формы может быть асинхронным. Другое дело, что, например, не все обработчики логически получится сделать асинхронными.
      Если процедура – обработчик события должна через параметры передать какие-то данные вызывающему ее коду, нельзя описывать ее с модификатором Асинх, поскольку в таких процедурах параметры всегда передаются по значению. Отказ или СтандартнаяОбработка, установленные внутри такой процедуры, не будут “видны” вызывающему коду.

      Вместо асинхронной функции в данном случае можно использовать “классические” методы:

      &НаКлиенте
      Процедура СписокПередНачаломДобавления(Элемент, Отказ, Копирование, Родитель, Группа, Параметр)
          Отказ = Истина;
          ПоказатьПредупреждение(, "Текст");
      КонецПроцедуры
      • Борис

        Спасибо, понимание постепенно приходит:)

        Можете еще пожалуйста пояснить последнее предложение.
        «Отказ или СтандартнаяОбработка, установленные внутри такой процедуры» – эта часть понятна.
        «не будут “видны” вызывающему коду» – что есть вызывающий код?

        • Василий Ханевич

          В данном случае это будет код платформы, вызывающий указанную процедуру-обработчик события.

          В статье, в указанном разделе есть описание этого момента на примере процедуры ПередЗакрытием:
          При этом выполнение процедуры ПередЗакрытием приостанавливается, управление возвращается к коду, вызвавшему этот метод. Поскольку процедуру ПередЗакрытием вызвала платформа, управление возвращается платформе. Параметр Отказ в коде не был изменен, кроме того любые изменения параметра в самой асинхронной процедуре “не видны” тому коду, который вызвал эту асинхронную процедуру, потому что параметры в нее передаются по значению. Значит, при возвращении управления платформе параметр Отказ принимает значение Ложь, поэтому форма закрывается. Вопрос пользователю отображается, но когда пользователь ответит на вопрос, форма уже закрыта, отменить ее закрытие невозможно.
          Эта особенность асинхронных обработчиков событий может оказаться неожиданной. Поэтому если процедура – обработчик события должна через параметры передать какие-то данные вызывающему ее коду, нельзя описывать ее с модификатором Асинх из-за описанной выше особенности.

          Аналогично и в Вашем примере платформа вызывает процедуру СписокПередНачаломДобавления.

  11. Pavel Shilkin

    Действительно, раньше в конце статьи для авторизованных была возможность сохранить статью в PDF.

    • Кузьмин Сергей

      Сейчас такой возможности нет.
      Но в дальнейшем рассмотрим возможность скачивать статьи.

  12. evsob

    Хотелось бы иметь возможность эту статью и подобные ей выводить на печать, как сделано это на сайте Инфостарта

    • Юлия Волкогонова

      Добрый день.
      Хорошо, спасибо, передадим коллегам, которые отвечают за статьи.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Вход на сайт

Зарегистрироваться

Подтверждение регистрации будет отправлено на указанный e-mail.

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

Восстановить доступ

E-mail или логин

Ссылка на создание нового пароля будет отправлена на указанный e-mail.