При разработке многопользовательских систем в среде 1С:Предприятие программисты часто сталкиваются с классической задачей синхронизации интерфейсов. Представим ситуацию: в Сеансе 1 у пользователя открыта карточка элемента справочника (например, контрагента или номенклатуры). В этот же момент в Сеансе 2 (который может быть как другим пользователем, так и фоновым заданием или интеграционным обменом) этот же элемент перезаписывается и его данные меняются.
Стандартный метод Оповестить() работает исключительно локально — в рамках текущего клиентского сеанса. Он физически не способен передать сигнал в другой запущенный экземпляр программы, так как клиентские контексты разных пользователей полностью изолированы друг от друга. Попытка решить эту задачу «в лоб» через стандартные события формы не принесет желаемого результата.
В этой статье мы подробно проанализируем архитектуру межсеансового взаимодействия в 1С, разберем ограничения платформы и рассмотрим четыре практических сценария решения этой задачи: от классических обработчиков ожидания до современного механизма уведомлений и Системы взаимодействия.
---
Начиная с версии платформы 8.3.26, разработчики получили долгожданный и крайне эффективный инструмент — встроенный механизм отправки уведомлений с сервера на клиент без необходимости развертывания тяжелой инфраструктуры Системы взаимодействия. Данная возможность доступна «из коробки» на любых лицензиях (включая ПРОФ) и работает напрямую через сетевые протоколы платформы.
Давайте разберем по шагам, как настроить сквозное оповещение с использованием глобального свойства УведомленияКлиента (объект типа МенеджерУведомленийКлиента).
Шаг 1. Подписка на уведомление в форме (Сеанс 1)
В модуле управляемой формы, которую нам необходимо динамически обновлять, при открытии регистрируем обработчик получения нашего кастомного сообщения:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Подключаем прослушивание события "ИзменениеДанныхСправочника"
УведомленияКлиента.ПодключитьОбработчик(
"ИзменениеДанныхСправочника",
Новый ОписаниеОповещения("ОбработатьПолучениеСигнала", ЭтотОбъект)
);
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьПолучениеСигнала(ДанныеУведомления, ДополнительныеПараметры) Экспорт
// Проверяем, что измененный объект — это именно тот элемент, который сейчас открыт в форме
Если ДанныеУведомления.Свойство("Ссылка") И ДанныеУведомления.Ссылка = Объект.Ссылка Тогда
// Если пользователь сам не редактирует форму (нет модифицированности), перечитываем данные
Если Не Модифицированность Тогда
Прочитать();
ПоказатьОповещениеПользователя("Данные обновлены", , "Этот элемент был изменен в другом сеансе.");
Иначе
// Если форма модифицирована, предупреждаем о конфликте версий
ПоказатьПредупреждение(, "Внимание! Данные этого объекта были изменены другим пользователем. Рекомендуется сохранить свои изменения или переоткрыть форму.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Шаг 2. Отправка уведомления при записи (Сеанс 2)
В модуле объекта справочника или в обработчике события, который выполняет программную запись, мы инициируем отправку сообщения во все активные сеансы:
// В модуле объекта справочника в обработчике ПриЗаписи
Процедура ПриЗаписи(Отказ)
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Формируем структуру с параметрами измененного объекта
ДанныеДляОтправки = Новый Структура("Ссылка", ЭтотОбъект.Ссылка);
// Отправляем уведомление всем подключенным клиентам информационной базы
УведомленияКлиента.ОтправитьУведомление("ИзменениеДанныхСправочника", ДанныеДляОтправки);
КонецПроцедуры
Важное преимущество метода: Платформа берет на себя всю низкоуровневую работу по маршрутизации сообщений. Сигнал доставляется практически мгновенно, не нагружая СУБД фоновыми запросами.
---
Если ваша конфигурация работает на более ранних версиях платформы (до 8.3.26), но в системе развернута и настроена Система взаимодействия (через облачный сервис 1С:Диалог или локальный сервер взаимодействия), мы можем задействовать ее программный интерфейс (API).
Система взаимодействия позволяет не только организовывать чаты между пользователями, но и программно отправлять сообщения в фоновом режиме, вызывая обработчики кода на клиенте. Этот механизм часто называют «вызовом клиентского контекста с сервера».
Рассмотрим логику реализации:
Прочитать() для обновления экранных полей.Этот подход невероятно быстр и надежен, однако имеет инфраструктурные ограничения: требуется развертывание дополнительного ПО (сервер взаимодействия на Java с базами данных Elasticsearch и PostgreSQL) или наличие постоянного интернет-доступа для работы с бесплатным облачным сервером 1С. Кроме того, при копировании информационных баз настройки интеграции могут потребовать повторной инициализации.
---
Вместо того чтобы решать задачу «как обновить форму, когда данные уже изменились», мы можем пойти по превентивному пути: не дать второму сеансу изменить данные, пока первый сеанс держит форму открытой.
В платформе 1С для этих целей предназначен механизм пессимистической блокировки данных формы. Давайте посмотрим, как это реализовать.
В форме элемента справочника при ее открытии вызовем метод управляемой формы ЗаблокироватьДанныеФормыДляРедактирования():
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Попытка
// Пытаемся заблокировать объект для редактирования сразу при открытии формы
ЗаблокироватьДанныеФормыДляРедактирования();
Исключение
// Если объект уже заблокирован в другом сеансе, платформа вызовет исключение
ПоказатьОповещениеПользователя("Внимание", , "Данный объект уже редактируется в другом сеансе. Открытие формы в режиме редактирования невозможно.");
// Мы можем либо закрыть форму, либо открыть ее только на просмотр
ТолькоПросмотр = Истина;
КонецПопытки;
КонецПроцедуры
Если Сеанс 1 успешно выполнил данный метод, то при любой попытке Сеанса 2 изменить этот элемент (хоть интерактивно, хоть программно через вызов метода СправочникОбъект.Заблокировать()) система выдаст ошибку блокировки данных. Это гарантирует, что пользователь в Сеансе 1 не потеряет результаты своей работы из-за неожиданной перезаписи объекта «за его спиной».
Минус подхода: Блокировка держится все время, пока у пользователя открыта форма. Если сотрудник откроет карточку контрагента и уйдет на обед, этот объект будет заблокирован для всех остальных процессов в системе на весь период его отсутствия. Используйте этот метод аккуратно и только там, где монопольный доступ действительно необходим.
---
Если использование современных механизмов (Системы взаимодействия или платформы 8.3.26+) по каким-то причинам невозможно, остается классический «костыль», проверенный годами — периодический опрос сервера по таймеру.
Идея заключается в том, что клиентское приложение с определенным интервалом времени (например, каждые 10–15 секунд) «ходит» на сервер и проверяет, не изменилась ли версия открытого объекта.
Давайте реализуем эту схему оптимально, чтобы минимизировать нагрузку на систему.
Шаг 1. Инициализация обработчика ожидания на клиенте
При открытии формы запускаем фоновый таймер:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Подключаем проверку обновлений каждые 15 секунд
ПодключитьОбработчикОжидания("ПроверитьОбновлениеОбъектаНаСервере", 15);
КонецПроцедуры
&НаКлиенте
Процедура ПроверитьОбновлениеОбъектаНаСервере()
// Если пользователь уже начал вносить изменения в форму, автообновление делать нельзя,
// иначе мы затрем введенные им данные.
Если Модифицированность Тогда
Возврат;
КонецЕсли;
// Передаем ссылку и текущую версию объекта для проверки на сервер
Если ОбъектИзмененНаСервере(Объект.Ссылка, Объект.ВерсияДанных) Тогда
// Перечитываем данные из БД в форму
Прочитать();
ПоказатьОповещениеПользователя("Информация", , "Данные формы были автоматически обновлены.", БиблиотекаКартинок.Информация);
КонецЕсли;
КонецПроцедуры
Шаг 2. Сверхлегкая серверная функция проверки версии
Нам важно сделать серверный вызов максимально легким, чтобы он не выполнял тяжелых запросов к СУБД и не считывал весь объект целиком.
&НаСервереБезКонтекста
Функция ОбъектИзмененНаСервере(СсылкаНаОбъект, ТекущаяВерсияДанных)
// Если ссылка пустая (новый элемент) — проверять нечего
Если Не ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
Возврат Ложь;
КонецЕсли;
// Выполняем быстрый точечный запрос только к системному полю ВерсияДанных
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.ВерсияДанных КАК ВерсияДанных
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
// Получаем строку версии из базы данных
ВерсияВБД = ПолучитьСтрокуИзДвоичныхДанных(Выборка.ВерсияДанных);
// Сравниваем ее с версией, которая сейчас отображается на клиенте
Возврат (ВерсияВБД <> ТекущаяВерсияДанных);
КонецЕсли;
Возврат Ложь;
КонецФункции
Критический недостаток опроса по таймеру: Если в базе одновременно работают сотни пользователей и у каждого открыто по несколько таких форм, постоянные «холостые» запросы к серверу и СУБД создадут колоссальную паразитную нагрузку на систему и сеть. Прибегайте к этому методу только в крайних случаях и делайте интервал опроса как можно больше.
---
Подводя итог анализу механизмов межсеансового оповещения, мы можем составить следующую матрицу применимости решений:
УведомленияКлиента. Он прост в реализации, быстр и не требует настройки стороннего ПО.ЗаблокироватьДанныеФормыДляРедактирования().ВерсияДанных, чтобы не перегружать СУБД.