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