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