Как автоматически обновить форму списка у одного пользователя при изменении данных другим в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе многопользовательской работы в системах 1С:Предприятие часто возникает классическая архитектурная задача: один пользователь открыл форму списка (например, список заказов или реализаций), а второй пользователь в этот же момент создает, изменяет или проводит документ из этого списка. По умолчанию первый пользователь не увидит изменений до тех пор, пока вручную не нажмет клавишу F5, не выберет команду «Обновить» в меню или не переоткроет форму. Заставлять пользователей постоянно совершать лишние действия вручную — неэффективно, поэтому перед разработчиком встает задача автоматизировать этот процесс.

В данной статье мы подробно разберем, почему стандартные методы локального оповещения здесь не работают, и проанализируем четыре практических способа реализации сквозного обновления данных между сеансами разных пользователей: от простейших штатных механизмов до современных высокотехнологичных инструментов платформы.

Почему не работает стандартный метод ОповеститьОбИзменении()?

Часто начинающие разработчики пытаются решить задачу «в лоб», используя метод ОповеститьОбИзменении() или процедуру Оповестить(). Разберем причину, почему этот подход обречен на неудачу в многопользовательской среде.

Дело в том, что методы глобального модуля ОповеститьОбИзменении() и Оповестить() работают исключительно в рамках одного и того же клиентского сеанса. Они используют локальный событийный цикл конкретного запущенного приложения (тонкого, толстого или веб-клиента). Когда Пользователь А записывает документ, вызов этих методов отправляет сигнал в локальную память его собственного сеанса. Сеанс Пользователя Б, запущенный на совершенно другом компьютере (или даже на том же терминальном сервере, но под другой учетной записью), об этом событии ничего не знает, так как сеансы изолированы друг от друга из соображений безопасности и производительности.

Для того чтобы передать сигнал из одного сеанса в другой, требуется участие промежуточного звена — сервера приложений 1С или системы управления базами данных (СУБД). Рассмотрим рабочие методы реализации такой связи.


Способ 1. Стандартный период автообновления динамического списка

Самый простой и не требующий написания сложного программного кода способ — использование встроенного свойства динамического списка. Платформа 1С:Предприятие умеет самостоятельно с заданной периодичностью опрашивать базу данных на предмет изменений.

Рассмотрим по шагам, как настроить автообновление:

  1. Откроем форму списка нужного объекта в конфигураторе.
  2. Выделим основной реквизит формы, имеющий тип ДинамическийСписок (обычно он называется Список).
  3. В панели свойств найдем свойство ПериодАвтоОбновления.
  4. Установим требуемый интервал обновления в секундах (минимально допустимое платформой значение — 10 секунд).

Мы также можем управлять этим свойством динамически из встроенного языка. Например, включим автообновление при открытии формы:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // Устанавливаем интервал автообновления списка в 30 секунд
    Элементы.Список.ПериодАвтоОбновления = 30;
КонецПроцедуры

Оценим преимущества и недостатки этого подхода:


Способ 2. Современный механизм «Уведомления клиента» (начиная с версии 8.3.26)

В версии платформы 1С:Предприятие 8.3.26 появился долгожданный, специализированный инструмент, созданный как раз для решения подобных задач без использования Системы взаимодействия. Этот механизм позволяет серверу точечно отправлять гарантированные сообщения напрямую в конкретные активные клиентские сеансы.

Проанализируем схему реализации данного подхода:

Шаг 1. Подписка на уведомления на клиенте (в форме списка)

При открытии формы списка мы должны зарегистрировать наш клиентский сеанс в качестве получателя определенного типа уведомлений.


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Создаем описание обработчика, который сработает при получении сигнала от сервера
    ОписаниеОбработчика = Новый ОписаниеОповещения("ПриПолученииУведомленияОбновления", ЭтотОбъект);
    
    // Подписываемся на системный канал уведомлений
    УведомленияКлиента.ПодключитьОбработчик("ОбновлениеЗаказовПокупателей", ОписаниеОбработчика);
КонецПроцедуры

&НаКлиенте
Процедура ПриПолученииУведомленияОбновления(ИмяУведомления, ПараметрыУведомления, ДополнительныеПараметры) Экспорт
    // Проверяем имя пришедшего события
    Если ИмяУведомления = "ОбновлениеЗаказовПокупателей" Тогда
        // Программная команда на обновление динамического списка на форме
        Элементы.Список.Обновить();
    КонецЕсли;
КонецПроцедуры

Шаг 2. Отправка уведомления с сервера (при записи документа)

В модуле документа, изменения которого должны инициировать обновление, или в подписке на событие ПриЗаписи, мы прописываем отправку сообщения:


Процедура ПриЗаписи(Отказ)
    Если Не Отказ Тогда
        // Подготавливаем сопутствующие параметры (например, ссылку на измененный объект)
        ПараметрыСообщения = Новый Структура;
        ПараметрыСообщения.Вставить("ИдентификаторОбъекта", Ссылка);
        
        // Отправляем уведомление всем активным сеансам пользователей
        УведомленияКлиента.ОтправитьУведомление("ОбновлениеЗаказовПокупателей", ПараметрыСообщения);
    КонецЕсли;
КонецПроцедуры

При необходимости мы можем ограничить круг получателей, передав в метод третий параметр — массив конкретных номеров сеансов информационный базы (их можно получить через метод ПолучитьСеансыИнформационнойБазы()), чтобы не беспокоить пользователей, у которых данная форма не открыта.

Преимущества метода: Абсолютное отсутствие лишней нагрузки на СУБД. Обновление происходит мгновенно и только тогда, когда данные реально изменились. Платформа берет на себя все заботы по доставке сообщений даже при кратковременном разрыве связи.


Способ 3. Использование Системы взаимодействия как «шины событий»

Если ваша версия платформы ниже 8.3.26, но в компании развернута и активно используется Система взаимодействия (встроенный мессенджер 1С), вы можете применить её программный интерфейс (API) для пересылки служебных сообщений между сеансами.

В данном сценарии мы создаем скрытое неконтекстное обсуждение, которое будет выполнять роль внутренней шины обмена сообщениями:

Шаг 1. Подписка на сообщения Системы взаимодействия на клиенте

В кодовой части формы списка инициализируем прослушивание сообщений:


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Подключаем обработчик ожидания для безопасного подключения к Системе взаимодействия
    ПодключитьОбработчикОжидания("ИнициализироватьСистемуВзаимодействия", 1, Истина);
КонецПроцедуры

&НаКлиенте
Процедура ИнициализироватьСистемуВзаимодействия()
    // Указываем процедуру, которая будет принимать все входящие сообщения системы взаимодействия
    Обработчик = Новый ОписаниеОповещения("ПриПолученииСообщенияОбсуждения", ЭтотОбъект);
    СистемаВзаимодействия.ПодключитьОбработчикПолученияСообщений(Обработчик);
КонецПроцедуры

&НаКлиенте
Процедура ПриПолученииСообщенияОбсуждения(Сообщение, Отказ) Экспорт
    // Проверяем, является ли сообщение служебным сигналом для обновления нашей формы
    Если Сообщение.Текст = "REFRESH_ORDER_LIST" Тогда
        Элементы.Список.Обновить();
    КонецЕсли;
КонецПроцедуры

Шаг 2. Отправка сообщения с сервера при сохранении данных

В модуле формы изменяемого документа или в его серверном модуле пишем код отправки сигнала:


&НаСервере
Процедура ПослеЗаписиНаСервере(ТекущийОбъект, ПараметрыЗаписи)
    // Находим служебное обсуждение по его уникальному ключу
    КлючОбсуждения = "СлужебноеОбсуждениеОбновленияДанных";
    ВыборкаОбсуждений = СистемаВзаимодействия.ПолучитьОбсуждения(КлючОбсуждения);
    
    ИдентификаторОбсуждения = Неопределено;
    Если ВыборкаОбсуждений.Количество() > 0 Тогда
        ИдентификаторОбсуждения = ВыборкаОбсуждений[0].Идентификатор;
    Иначе
        // Если обсуждение еще не создано, инициализируем его программно
        НовоеОбсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
        НовоеОбсуждение.Ключ = КлючОбсуждения;
        НовоеОбсуждение.Наименование = "Служебный канал";
        НовоеОбсуждение.Записать();
        ИдентификаторОбсуждения = НовоеОбсуждение.Идентификатор;
    КонецЕсли;
    
    // Формируем и отправляем системное сообщение
    Сообщение = СистемаВзаимодействия.СоздатьСообщение(ИдентификаторОбсуждения);
    Сообщение.Текст = "REFRESH_ORDER_LIST";
    Сообщение.Записать();
КонецПроцедуры

Важное примечание: Использование Системы взаимодействия бесплатно при работе через облачный сервис 1С (1c.dialog.ru). Однако, если вы разворачиваете собственный локальный сервер Системы взаимодействия внутри закрытого контура предприятия, для его легального использования вам потребуются лицензии уровня КОРП.


Способ 4. Классическая «Очередь изменений» через регистр сведений

Рассмотрим универсальный «обходной» путь, который идеально подходит для абсолютно любых версий платформы 1С:Предприятие 8, работает в автономном режиме без интернета и не требует КОРП-лицензий. Этот алгоритм позволяет избежать «холостых» перезапросов к тяжелым таблицам СУБД, перенося нагрузку на один легкий, специально спроектированный регистр.

Шаг 1. Создание вспомогательного объекта конфигурации

Создадим в конфигураторе новый непериодический независимый регистр сведений под названием ОчередьОбновлений.

Шаг 2. Фиксация факта записи объекта на сервере

В модуле нашего документа при его успешной записи фиксируем время изменения таблицы:


Процедура ПриЗаписи(Отказ)
    Если Не Отказ Тогда
        // Записываем актуальное время изменения в служебный регистр
        МенеджерЗаписи = РегистрыСведений.ОчередьОбновлений.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.ИмяТаблицы = "Документ.РеализацияТоваровУслуг";
        МенеджерЗаписи.ДатаПоследнегоИзменения = ТекущаяДатаСеанса();
        МенеджерЗаписи.Записать(Истина); // Записываем поверх старого значения
    КонецЕсли;
КонецПроцедуры

Шаг 3. Организация опроса на клиенте в форме списка

В модуле формы списка создадим переменную для хранения локального времени последнего обновления и настроим легкий обработчик ожидания:


&НаКлиенте
Перем ВремяЛокальногоОбновления;

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // Фиксируем стартовое время открытия формы
    ВремяЛокальногоОбновления = ТекущаяДата();
    
    // Подключаем циклическую проверку каждые 15 секунд
    ПодключитьОбработчикОжидания("ПроверитьНеобходимостьОбновления", 15);
КонецПроцедуры

&НаКлиенте
Процедура ПроверитьНеобходимостьОбновления() Экспорт
    // Выполняем быстрый внеконтекстный запрос к серверу
    Если БылиИзмененияВБазе(ВремяЛокальногоОбновления) Тогда
        // Если зафиксировано изменение, обновляем динамический список
        Элементы.Список.Обновить();
        // Обновляем локальную метку времени
        ВремяЛокальногоОбновления = ТекущаяДата();
    КонецЕсли;
КонецПроцедуры

&НаСервереБезКонтекста
Функция БылиИзмененияВБазе(ВремяЛокальногоОбновления)
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ Первые 1
        |	Истина КАК Изменено
        |ИЗ
        |	РегистрСведений.ОчередьОбновлений КАК ОчередьОбновлений
        |ГДЕ
        |	ОчередьОбновлений.ИмяТаблицы = ""Документ.РеализацияТоваровУслуг""
        |	И ОчередьОбновлений.ДатаПоследнегоИзменения > &ВремяЛокальногоОбновления";
    
    Запрос.УстановитьПараметр("ВремяЛокальногоОбновления", ВремяЛокальногоОбновления);
    РезультатЗапроса = Запрос.Выполнить();
    
    Возврат Не РезультатЗапроса.Пустой();
КонецФункции

Почему это эффективнее стандартного автообновления?

При стандартном автообновлении платформа каждые 15 секунд выполняет тяжелый SQL-запрос динамического списка с кучей соединений, виртуальных таблиц и сложных условий отбора. В схеме с регистром сведений обработчик ожидания раз в 15 секунд выполняет сверхлегкий и мгновенный запрос по индексу к одной-единственной строке маленькой таблицы. Реальное (тяжелое) обновление списка происходит только тогда, когда функция БылиИзмененияВБазе() действительно возвращает значение Истина.


Резюме и рекомендации по выбору решения

Мы проанализировали четыре принципиально разных подхода к реализации обновления интерфейса между сеансами. Выбор конкретного метода напрямую зависит от технических условий вашего проекта:

  1. Если вы используете самую современную версию платформы (1С:Предприятие 8.3.26 и выше), то однозначно лучшим выбором будет использование встроенного механизма УведомленияКлиента. Он максимально производителен и не имеет лишних зависимостей.
  2. Если в вашей организации развернут корпоративный контур и активно внедрена Система взаимодействия, используйте её в качестве транспортной шины для передачи служебных сигналов обновления форм.
  3. В случае работы на платформе версий от 8.3.11 до 8.3.25 без возможности покупки КОРП-лицензий лучшим выбором с точки зрения баланса производительности и трудозатрат станет схема с регистром сведений «Очередь изменений».
  4. Если список пользователей невелик (до 5-10 человек), а база данных работает на производительном оборудовании, вы можете смело использовать стандартный ПериодАвтоОбновления, чтобы сэкономить время на разработку.
← На главную