При разработке сложных интерфейсов в 1С программисты часто сталкиваются с проблемой долгого открытия форм. Типичная ситуация: при открытии списка номенклатуры необходимо рассчитать дополнительные параметры, цены или остатки. Если делать это синхронно в процедуре ПриОткрытии или ПередОткрытием, интерфейс «замирает», и пользователь вынужден ждать несколько секунд. В этой статье мы разберем, почему ключевое слово Асинх не всегда спасает ситуацию, и как правильно перенести тяжелые расчеты в фоновый поток.
Рассмотрим распространенное заблуждение: многие разработчики полагают, что использование конструкции Асинх Функция автоматически создает новый поток исполнения (как в некоторых других языках программирования). Проанализируем пример кода, который часто приводит к разочарованию:
&НаКлиенте
Асинх Процедура КнопкаВыполнитьНажатие(Кнопка)
// Ожидается, что это выполнится параллельно
Результат = ВывестиСообщениеОЗначенииИтератораАсинх();
Сообщить("123");
Сообщить(Ждать Результат);
КонецПроцедуры
&НаКлиенте
Асинх Функция ВывестиСообщениеОЗначенииИтератораАсинх()
Сч = 0;
Пока Сч < 1000000 Цикл
Сч = Сч + 1;
КонецЦикла;
Возврат("Значение итератора = " + Сч);
КонецФункции
В данном случае код все равно будет выполняться синхронно. Выясним причину: в 1С клиентское приложение работает в одном потоке. Ключевое слово Асинх — это лишь способ описать логику ожидания внешних событий (например, открытия окна или ответа от веб-сервиса) без блокировки интерфейса. Однако тяжелые математические циклы или формирование таблиц значений внутри такой функции по-прежнему занимают основной поток. Пока цикл Пока ... Цикл не завершится, 1С не перейдет к строке Сообщить("123").
Важный вывод: асинхронные функции предназначены для неблокирующего ожидания (оператор Ждать), а не для параллельных вычислений на клиенте.
Чтобы форма открылась мгновенно, а данные «подтянулись» позже, необходимо использовать механизм фоновых заданий. Это единственный надежный способ запустить тяжелый запрос или расчет на сервере параллельно работе пользователя.
Разберем алгоритм реализации для обычных форм по шагам:
ПриОткрытии запускаем фоновое задание на сервере.ПоместитьВоВременноеХранилище, чтобы передать параметры для расчета в фоновое задание.ПодключитьОбработчикОжидания.Посмотрим на пример реализации запуска задания:
Процедура ПриОткрытии()
// Создаем уникальный адрес для получения результата
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, ЭтаФорма.УникальныйИдентификатор);
// Запускаем расчет на сервере
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(АдресРезультата);
ФоновыеЗадания.Выполнить("МойОбщийМодуль.РассчитатьДанныеДляФормы", МассивПараметров);
// Включаем проверку готовности каждые 2 секунды
ПодключитьОбработчикОжидания("ПроверитьЗавершениеРасчета", 2);
КонецПроцедуры
В серверном методе РассчитатьДанныеДляФормы мы выполняем запрос и помещаем результат обратно в хранилище по переданному адресу. Таким образом, форма открывается сразу, пользователь может просматривать список, а через пару секунд данные в таблице обновятся автоматически.
Часто причиной задержек является не только синхронность, но и неэффективный текст запроса. Рассмотрим пример запроса автора темы и проанализируем критические ошибки производительности. Проблема заключается в использовании виртуальных таблиц СрезПоследних внутри ЛЕВОЕ СОЕДИНЕНИЕ.
Почему это медленно? Когда вы соединяете справочник напрямую с виртуальной таблицей среза без параметров, СУБД может пытаться сначала рассчитать срез для ВСЕХ записей регистра, а потом соединять их. Это крайне ресурсоемкая операция.
Рассмотрим, как оптимизировать данный запрос:
// Вместо прямого соединения со срезом в цикле:
// 1. Выгружаем нужные ссылки во временную таблицу (ВТ)
// 2. Получаем срез последних, используя ВТ в качестве фильтра в параметрах виртуальной таблицы
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Ссылка ПОМЕСТИТЬ ВТ_Номенклатура ИЗ Справочник.Номенклатура ГДЕ ...;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Цены.Цена,
| Курсы.Курс,
| ВТ.Ссылка
|ИЗ
| ВТ_Номенклатура КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Дата, Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВТ_Номенклатура)) КАК Цены
| ПО ВТ.Ссылка = Цены.Номенклатура";
Обратите внимание на использование фильтра Номенклатура В (ВЫБРАТЬ Ссылка ИЗ ВТ_Номенклатура) внутри параметров СрезПоследних. Это заставляет систему выбирать данные только по тем записям, которые реально нужны для текущей формы.
Проанализируем еще несколько важных технических моментов:
ТаблицуЗначений в реквизитах формы. Помните, что если вы используете таблицу на форме для отображения (в ТабличномПоле), то платформа берет на себя управление данными. Программная индексация через ТЗ.Индексы.Добавить("Ссылка") полезна только в том случае, если вы планируете выполнять поиск строк методом Найти() или НайтиСтроки() в программном коде. Для визуальной сортировки или поиска пользователем она не требуется.СпрНоменклатура.Ссылка.Производитель. Старайтесь избегать двойного обращения через точку в запросе (от ссылки к реквизиту), так как это порождает лишнее неявное соединение таблиц в SQL. Правильно писать: СпрНоменклатура.Производитель.ПриВыводеСтроки или механизм динамических списков в управляемых формах).Таким образом, для решения задачи автора необходимо отказаться от попыток использовать Асинх для вычислений и перевести формирование ТаблицыЗначений в фоновое задание, попутно оптимизировав структуру запроса к регистрам сведений.