В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с особенностью работы управляемого интерфейса: метод Сообщить() не выводит информацию на экран мгновенно. В отличие от обычного приложения, где сообщения появлялись в окне сообщений сразу по мере выполнения кода, в управляемых формах они буферизуются. Платформа накапливает их и передает на клиентскую часть только после того, как управление полностью вернется к интерфейсному потоку (например, по завершении серверного вызова или клиентской процедуры). Это создает значительные неудобства при выполнении длительных циклов обработки данных, когда пользователю необходимо видеть прогресс или лог операций в реальном времени.
В данной статье мы разберем причины такого поведения платформы и рассмотрим несколько способов решения этой задачи: от официально рекомендованных до «хитрых» синхронных методов.
Проанализируем архитектуру управляемого приложения. Основной принцип здесь — минимизация количества вызовов между клиентом и сервером. Когда мы вызываем серверную процедуру, 1С открывает своего рода «сессию обмена». Все данные, которые должны быть переданы пользователю (тексты сообщений, изменения реквизитов формы, команды открытия новых окон), упаковываются в один пакет. Этот пакет отправляется клиенту только тогда, когда выполнение кода на сервере завершено и управление передается обратно на сторону клиента. Пока ваш цикл Для Счетчик = 1 По 20000 работает, пакет остается открытым, и клиентское приложение «не знает», что в буфере уже лежат сообщения.
Рассмотрим наиболее правильный с точки зрения архитектуры 1С подход. Чтобы основной интерфейс не «замирал» и мог отображать прогресс, длительную операцию необходимо вынести в фоновое задание. Выясним, как это реализовать:
ФоновыеЗадания.Выполнить().ПодключитьОбработчикОжидания()), который с периодичностью в 1–2 секунды опрашивает состояние задания.ПолучитьСообщенияПользователю().Этот способ позволяет пользователю видеть сообщения по мере их генерации, не блокируя основной поток интерфейса. Однако, если вы работаете с внешней обработкой, создание фонового задания может потребовать дополнительных манипуляций (например, передачу текста модуля или использование временного хранилища).
Если вам не нужен детальный лог из тысячи строк, а достаточно информировать пользователя о текущем этапе, проанализируем возможности метода Состояние(). В отличие от Сообщить(), метод Состояние() в клиентском коде может обновлять информацию в строке состояния чуть более оперативно, однако он всё равно подвержен общим правилам отрисовки интерфейса. Рассмотрим пример использования:
Для Счетчик = 1 По 100 Цикл
// Выполняем полезную нагрузку
Состояние("Обработка данных...", Счетчик, "Выполнено " + Счетчик + "%");
// В клиентском коде это сработает эффективнее, чем Сообщить()
КонецЦикла;
Разберем ситуацию, когда нам принципиально важно оставаться в рамках одного потока выполнения (синхронно). Существует возможность заставить платформу выполнить неявный серверный вызов, который спровоцирует перерисовку формы и «выброс» накопленного буфера сообщений на экран. Один из таких способов — манипуляция свойством Видимость элементов формы.
Посмотрим на алгоритм реализации:
Группа или Поле).
Для Счетчик = 1 По 20 Цикл
Сообщить("Итерация №" + Счетчик);
// Принудительная перерисовка
Элементы.ВспомогательныйЭлемент.Видимость = Ложь;
Элементы.ВспомогательныйЭлемент.Видимость = Истина;
// Имитация длительной работы
Для Счетчик2 = 1 По 100000 Цикл
Пустышка = Счетчик2;
КонецЦикла;
КонецЦикла;
Важное предостережение: Выясним, почему это решение называют «адским». Изменение видимости приводит к контекстному серверному вызову. Это означает, что на каждой итерации цикла весь контекст формы (все данные реквизитов) будет гоняться туда-обратно между клиентом и сервером. Кроме того, это приводит к очистке кэшей представлений ссылок и данных быстрого выбора. Используйте этот метод только в «легких» формах и с редкой периодичностью (например, раз в 10–50 итераций).
Рассмотрим продвинутый вариант для реализации Real-time лога. Если на форме разместить ПолеHTMLДокумента, мы можем обращаться к его содержимому через JavaScript. Движок рендеринга HTML в 1С часто работает более отзывчиво, чем стандартные элементы управления.
Разберем шаги реализации:
ПолеЛога с типом Строка и видом Поле HTML документа.DOM-дерево.
// Пример вызова из 1С
ТекстСкрипта = "var p = document.createElement('p'); p.innerHTML = '" + ТекущееСообщение + "'; document.body.appendChild(p);";
Элементы.ПолеЛога.Документ.parentWindow.eval(ТекстСкрипта);
Поскольку управление HTML-документом происходит через прямое обращение к объектам COM (в Windows-клиенте) или внутренним методам браузера, это может позволить обновлять картинку без завершения основного серверного вызова 1С.
Существует еще один метод — ОтобразитьИзменениеДанных(). Первоначально он предназначен для оповещения других открытых форм о том, что данные в базе изменились. Но, как заметили разработчики, его вызов также провоцирует клиент-серверную синхронизацию.
Сообщить(Счетчик);
ОтобразитьИзменениеДанных(СсылкаНаОбъект, ВидИзмененияДанных.Изменение);
Проанализируем последствия: данный метод также выполняет обращение к серверу и очищает кэши на клиенте. Его преимущество перед «передергиванием» видимости в том, что он выглядит чуть более легитимно в коде, но технические последствия для производительности будут схожими.
Мы выяснили, что штатного, легкого и синхронного метода для мгновенного вывода Сообщить() в управляемом приложении не существует в силу архитектурных особенностей платформы.
Рекомендации по выбору:
ОтобразитьИзменениеДанных) допустимо только в исключительных случаях, понимая риски деградации производительности интерфейса.Помните, что избыточное количество сообщений (десятки тысяч строк) перегружает интерфейс и затрудняет работу пользователя. Хорошим тоном считается вывод только ключевых вех процесса или использование шкалы прогресса.