В данном материале мы рассмотрим отличие синхронных и асинхронных методов, а также приемы рефакторинга на примере нескольких часто используемых действий, таких как вывод предупреждения, вопрос пользователю или ввод пользователем каких-то значений.
Синхронные методы
Строго говоря, практически весь код в 1С – синхронный, т.е. выполняется последовательно оператор за оператором. Например, код:
А = 10; А = А + 10; Сообщить (А);
Соответственно, и большая часть методов платформы являются синхронными. Например, мы не можем асинхронно получить выборку из результата запроса или асинхронно свернуть таблицу значений. Проблемы начинают возникать, когда код 1С начинает взаимодействовать с внешним миром – например, с пользователем, с файловой подсистемой, с операционной системой или веб-сервисами. В этом случае платформа начинает ждать, пока внешний мир отреагирует, и выполнение кода на это время приостанавливается.
Например, файловая система занята какой-то тяжелой операцией, и не может сразу обработать нужный файл, или веб-приложение отвечает с задержкой, или пользователь должен ввести значение, но отошел выпить кофе. Платформе остается лишь ждать, и весь код ставится “на паузу”. Разумеется, речь идет о текущем сеансе конкретного пользователя.
Поэтому для обеспечения большей отзывчивости интерфейса разработчики платформы 1С создали асинхронные аналоги, которые не блокируют выполнение кода. К ним относятся например, методы ПоказатьВопрос, НачатьПомещениеФайлаНаСервер, и многие другие. Вместо приостановки выполнения кода асинхронные методы выполняются “в фоне”, и по завершении дают возможность обработать результат в специальном обработчике – “Обработке оповещения”. Такой подход использовался длительное время, да и сейчас в типовых конфигурациях и БСП он много где используется. В последних версиях платформы 1С добавила “синтаксического сахара” и сделала жизнь разработчиков проще, а код – читабельнее. Об этом читайте в соответствующих разделах статьи – про старый и новый подходы.
Синхронность и модальность в 1С - в чем разница?
Иногда возникает путаница в терминологии, но тут все просто. Модальные вызовы – это такое поведение интерфейса, когда вызывается открытие окна, и это одно окно блокирует все остальные. Т.е. помимо того что само выполнение кода поставлено на паузу, еще и интерфейс ждет реакции пользователя. Например, к модальным методам относятся Предупреждение, ОткрытьФормуМодально.
Можно долго дискутировать на предмет того, почему 1С решила отказываться от модальности – частично это связано с развитием UX, созданием более отзывчивого и дружелюбного интерфейса, частично – с технологическими особенностями реализации модальных окон в браузерах. Но имеем данность – в веб-клиенте нам не удастся в 1С открыть окно модально, а в типовых конфигурациях в свойствах конфигурации почти везде стоит “Режим использования модальности – Не использовать”.
Соответственно, наша как разработчика задача – корректно использовать немодальные аналоги привычных методов. Немодальные окна не приостанавливают выполнение основного потока выполнения, но могут блокировать интерфейс либо его часть. Например, окно выбора контрагента может блокировать окно документа-владельца, куда мы хотим подобрать этого контрагента, а все остальное будет доступно.
Асинхронные методы 1С - старый подход
Рассмотрим на примере метода “Предупреждение”. Это модальный синхронный метод глобального контекста. И все мы привыкли, что он выводит окошко с предупреждением, и система ждет таймаута либо пока пользователь нажмет “ОК”. В асинхронном варианте нам следует использовать метод ПоказатьПредупреждение. В случае, если нам нужно, чтобы после предупреждения отработал какой-то код, его следует вынести в экспортную клиентскую процедуру, и указать имя этой процедуры в обработке оповещения.
&НаКлиенте Процедура ПриОткрытии(Отказ) ПоказатьПредупреждение(Новый ОписаниеОповещения("ПослеПоказаПредупреждения", ЭтотОбъект), "Выведем предупреждение"); КонецПроцедуры &НаКлиенте Процедура ПослеПоказаПредупреждения(ДополнительныеПараметры) Экспорт А = 2; Б = А+2; КонецПроцедуры
Но такой подход имеет ряд недостатков. Во-первых, код может “размазываться” по нескольким модулям, так как обработка оповещения может находиться в другом клиентском модуле. Во-вторых, с ростом количества идущих подряд асинхронных вызовов количество и сложность кода увеличивается, и его становится трудно читать и неудобно поддерживать.
Например, мы сперва можем задать пользователю вопрос, затем предложить выбор файла для загрузки, после этого снова задать вопрос, после этого – предложить каталог для сохранения файла, и т.п. В результате ,например, для четырех асинхронных методов вместо одной процедуры мы получим восемь, что, согласитесь, не очень читабельно и удобно.
Асинх и Ждать - новый подход
Начиная с версии 8.3.18, в 1С появился новый подход для работы с асинхронностью – добавились ключевые слова Асинх и Ждать, а также появился новый тип – Обещание. Все это очень похоже на аналогичный механизм в JavaScript, и даже названия являются буквально переводом английских терминов Asinc, Await и Promise.
Работает все это примерно так. Мы объявляем метод асинхронным – пишем перед объявлением процедуры или функции слово Асинх. В коде такого метода мы можем использовать ключевое слово Ждать. Если использовать слово Ждать без слова Асинх, получим ошибку “Оператор Ждать (Await) может употребляться только в асинхронных процедурах или функциях”.
Теперь можно писать асинхронные методы в синхронном стиле. Например:
&НаКлиенте Асинх Процедура ПриОткрытии(Отказ) Ждать ПредупреждениеАсинх("Выведем предупреждение"); //Этот код отработает только после того как отработает асинхронный метод. А = 2; Б = А+2; ПредупреждениеАсинх("Выведем предупреждение"); //Этот код отработает сразу, т.к. нет слова Ждать В = Б+3; КонецПроцедуры
Важный нюанс. Выполнение асинхронных функций возвращает обещание, в том случае, если функция еще не завершила свою работу. И мы это обещание можем поместить в переменную, передать в какой-нибудь другой метод, и уже там ждать результат.
Также, мы можем использовать слово ждать для своих собственных асинхронных методов – тех, которые мы пишем со словом Асинх. Рассмотрим пример создания асинхронного обработчика команды, которая вызовет асинхронную функцию.
&НаКлиенте Асинх Процедура ПолучитьРезультатАсинхронно(Команда) Сообщить("Начало асинхронного вызова"); Результат = Ждать АсинхронныйРезультат(); Сообщить("Конец асинхронного вызова"); Сообщить("Получили результат асинхронно " + Результат); КонецПроцедуры &НаКлиенте Асинх Функция АсинхронныйРезультат() ВводПользователя = Ждать ВвестиЧислоАсинх(0); Сообщить("Получили результат " + ВводПользователя); Возврат ВводПользователя; КонецФункции
Приемы рефакторинга
В конфигураторе 1С есть некоторый инструментарий – не сказать чтоб очень богатый, но полезный – для рефакторинга кода. При помощи рефакторинга мы можем быстро преобразовать “старые” синхронные вызовы в асинхронные. Рассмотрим процедуру с использованием синхронных методов:
&НаКлиенте Процедура ПримерРефакторингаКода(Команда) Предупреждение("Тут важное сообщение"); Ответ = Вопрос("Уверены?", РежимДиалогаВопрос.ДаНет); Если Ответ = КодВозвратаДиалога.Да Тогда Число1 = ВвестиЧисло(0); Число2 = ВвестиЧисло(0); Результат = Число1*Число2; Сообщить(Результат); Иначе Предупреждение("Не уверен - не начинай!"); КонецЕсли; КонецПроцедуры
В любом месте модуля вызовем Рефакторинг / Нерекомендуемые синхронные вызовы. Там выбираем пункт Преобразовать вызовы модуля. Все синхронные вызовы в модуле будут преобразованы, и при необходимости будет создана цепочка процедур обработчиков ожидания:
&НаКлиенте Процедура ПримерРефакторингаКода(Команда) ПоказатьПредупреждение(Новый ОписаниеОповещения("ПримерРефакторингаКодаЗавершение4", ЭтаФорма), "Тут важное сообщение"); КонецПроцедуры &НаКлиенте Процедура ПримерРефакторингаКодаЗавершение4(ДополнительныеПараметры) Экспорт Ответ = Неопределено; ПоказатьВопрос(Новый ОписаниеОповещения("ПримерРефакторингаКодаЗавершение3", ЭтаФорма), "Уверены?", РежимДиалогаВопрос.ДаНет); КонецПроцедуры &НаКлиенте Процедура ПримерРефакторингаКодаЗавершение3(РезультатВопроса, ДополнительныеПараметры) Экспорт Ответ = РезультатВопроса; Если Ответ = КодВозвратаДиалога.Да Тогда ПоказатьВводЧисла(Новый ОписаниеОповещения("ПримерРефакторингаКодаЗавершение2", ЭтаФорма), 0); Иначе ПоказатьПредупреждение(Новый ОписаниеОповещения("ПримерРефакторингаКодаЗавершение", ЭтаФорма), "Не уверен - не начинай!"); КонецЕсли; КонецПроцедуры &НаКлиенте Процедура ПримерРефакторингаКодаЗавершение2(Число, ДополнительныеПараметры) Экспорт Число1 = (Число <> Неопределено); ПоказатьВводЧисла(Новый ОписаниеОповещения("ПримерРефакторингаКодаЗавершение1", ЭтаФорма, Новый Структура("Число1", Число1)), 0); КонецПроцедуры &НаКлиенте Процедура ПримерРефакторингаКодаЗавершение1(Число, ДополнительныеПараметры) Экспорт Число1 = ДополнительныеПараметры.Число1; Число2 = (Число <> Неопределено); Результат = Число1*Число2; Сообщить(Результат); КонецПроцедуры &НаКлиенте Процедура ПримерРефакторингаКодаЗавершение(ДополнительныеПараметры) Экспорт КонецПроцедуры
В таком коде достаточно легко запутаться, но увы, в платформе до версии 8.3.18 новые асинхронные методы недоступны.
Также мы можем преобразовать только один нужный нам метод в асинхронный. Для этого нужно выделить его и в меню Рефакторинг / Нерекомендуемые синхронные вызовы выбрать пункт Преобразовать вызов. В открывшемся окне можно изменить имя обработчика оповещения.
Если мы уже начали писать вызов асинхронного “старого” метода, и нам лень самим создавать обработчик оповещения, рефакторинг снова нам поможет. Например, напишем “ПоказатьВопрос”, и по правой кнопке выберем пункт Рефакторинг/Создать обработку оповещения. Платформа создаст обработку оповещения, и при необходимости перенесет туда код, идущий после нашего вопроса.
Что еще полезного есть в меню Рефакторинг? Любой фрагмент кода, который не содержит ключевое слово Возврат, мы можем выделить отдельный метод. При этом платформа сама анализирует используемые переменные и предлагает либо процедуру либо функцию. Работает это просто – выделяем нужный фрагмент, и в меню Рефакторинг выбираем Выделить фрагмент.
Также мы можем переименовать метод через рефакторинг, и платформа выполнит переименование во всех модулях, где этот метод вызывается. Важно: это может потребовать значительных ресурсов, в случае, если метод используется в большом количестве мест. И платформа нас об этом предупреждает:
Ну и в завершение. Все знают, что комментировать надо, что комментарии важны, а кроме того, особым образом оформленные комментарии к методу отображаются в контекстной подсказке при вызове этого метода.
Однако не все любят писать комментарии, и уж тем более мало кто любит оформлять описание к процедур и функций. И чтобы делать это было проще и быстрее, можно воспользоваться пунктом меню Рефакторинг/СоздатьОписаниеПроцедуры или СоздатьОписаниеФункции. Для этого становимся на имя метода и вызываем контекстное меню. В результате мы получим заготовку комментария, который можно дополнить, и он будет корректно отображаться в контекстной подсказке.
// Процедура - Выполнить обработку данных // // Параметры: // Данные - - // Параметры - - // Дополнительно - - //
Нам останется только дополнить описание, прописать типы параметров, и теперь контекстная подсказка к нашему методу будет информативнее и полезнее:
// Процедура - Выполнить обработку данных // // Параметры: // Данные - Массив - входящие данные // Параметры - Структура - параметры обработки данных // Дополнительно - Произвольный - вспомогательные данные //