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

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

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

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

Анализ исходной архитектуры: как данные мигрируют между формами

Рассмотрим сценарий, описанный в конфигурации «Учет в управляющих компаниях ЖКХ, ТСЖ и ЖСК». Из документа открывается вспомогательная общая форма для ввода сведений о собственности. При ее закрытии данные передаются обратно в документ следующим образом:


&НаСервере
Функция ПолучитьАдресВХранилище()
    СтруктураДанных = Новый Структура;
    СтруктураДанных.Вставить("Жилец", Жилец);
    СтруктураДанных.Вставить("Собственник", Собственник);
    СтруктураДанных.Вставить("ФИО", ФИОСобственника);
    СтруктураДанных.Вставить("ОснованиеПолученияВСобственность", ОснованиеПолученияВСобственность);
    СтруктураДанных.Вставить("ВидДокумента", ВидДокумента);
    СтруктураДанных.Вставить("НомерДокумента", НомерДокумента);
    СтруктураДанных.Вставить("ДатаДокумента", ДатаДокумента);
    
    Возврат ПоместитьВоВременноеХранилище(СтруктураДанных, Новый УникальныйИдентификатор);
КонецФункции

Процедура ПриЗакрытии(ЗавершениеРаботы)
    АдресВХранилище = ПолучитьАдресВХранилище();
    Оповестить("ПередачаСведенийОСобственностиПриУпрощенномУчете", АдресВХранилище);
КонецПроцедуры

В форме документа-родителя это оповещение перехватывается стандартным обработчиком:


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

Давайте разберем слабые места этой схемы и выясним, почему попытка «вклиниться» во временное хранилище из внешней обработки — не всегда правильный путь.

Критическая ошибка №1: Опасность утечки памяти при использовании UUID

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

ПоместитьВоВременноеХранилище(СтруктураДанных, Новый УникальныйИдентификатор);

Использование конструкции Новый УникальныйИдентификатор в качестве второго параметра — это серьезная архитектурная ошибка. Давайте разберем, как работает временное хранилище в 1С:

  1. Если во временное хранилище передается адрес, связанный с конкретной формой (например, ЭтаФорма.УникальныйИдентификатор), платформа автоматически очищает эти данные из памяти сервера сразу же, как только форма закрывается.
  2. Если же генерируется абсолютно новый, «случайный» идентификатор, не привязанный ни к какому объекту, платформа вынуждена хранить эти данные до конца сеанса пользователя.

Если пользователи будут активно работать с базой данных, открывая и закрывая эту форму десятки раз в день, неиспользуемые структуры данных будут накапливаться в оперативной памяти сервера rphost. При работе в многопользовательском режиме это неизбежно приведет к деградации производительности сервера приложений 1С.

Правильное решение: всегда привязывайте адрес к уникальному идентификатору формы-владельца или самой закрывающейся формы, если это предусмотрено логикой передачи:

ПоместитьВоВременноеХранилище(СтруктураДанных, ЭтаФорма.УникальныйИдентификатор);

Критическая ошибка №2: Почему внешняя обработка не реагирует на Оповестить()

Попытки разработчика добавить процедуру ОбработкаОповещения в модуль формы своей внешней обработки часто заканчиваются неудачей — точка останова внутри процедуры просто не срабатывает. Выясним причины этого поведения.

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

  1. Явная привязка обработчика в свойствах формы. Недостаточно просто написать процедуру с именем ОбработкаОповещения в коде модуля. Необходимо открыть форму внешней обработки в конфигураторе, перейти на панель свойств формы, найти событие «ОбработкаОповещения» и связать его с вашей процедурой в модуле. Только тогда платформа зарегистрирует обработчик.
  2. Активное состояние формы (Время жизни). Метод Оповестить() работает по принципу «выстрелил и забыл». Оповещение доставляется мгновенно и только тем формам, которые уже созданы и открыты в текущем сеансе на клиенте. Если вы открыли форму внешней обработки после того, как вспомогательное окно закрылось и отправило сигнал, ваша обработка ничего не получит — событие уже ушло.

Где на самом деле хранятся данные?

Пытаться перехватить данные «на лету» в момент их передачи через оперативную память — это избыточное усложнение архитектуры. Временное хранилище создается лишь для кратковременного транзита информации между диалоговыми окнами.

Посмотрим на процедуру, которая вызывается в документе после получения оповещения:


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

Как мы видим, данные переносятся непосредственно в табличную часть Главная самого документа, из которого вызывалась форма. Следовательно, при проведении или записи документа эти сведения сохраняются в базе данных навсегда (в самом документе или связанных регистрах сведений).

Для построения отчета или извлечения этих данных во внешней обработке нам не нужно перехватывать временное хранилище. Достаточно написать стандартный запрос к табличной части документа. Рассмотрим пример такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	Ссылка.Ссылка КАК ДокументСсылка,
    |	Ссылка.Дата КАК ДатаДокумента,
    |	Главная.Проживающий КАК Жилец,
    |	Главная.ОснованиеПолученияВСобственность КАК Основание,
    |	Главная.ВидДокументаСобственности КАК ВидДокумента,
    |	Главная.НомерДокументаСобственности КАК НомерДокумента,
    |	Главная.ДатаДокументаСобственности КАК ДатаДокументаСобственности
    |ИЗ
    |	Документ.НачислениеУслугЖКХ.Главная КАК Главная
    |ГДЕ
    |	Ссылка.Проведен"; // Укажите имя вашего вида документа

РезультатЗапроса = Запрос.Выполнить();

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

Рекомендуемый современный паттерн: ОписаниеОповещения (Callback)

Если вы хотите полностью отказаться от ненадежного связывания форм через временное хранилище и глобальное событие Оповестить(), используйте современный стандарт разработки 1С — механизм асинхронных вызовов через объект ОписаниеОповещения.

Рассмотрим, как переписать этот функционал по стандартам БСП (Библиотеки стандартных подсистем):

1. Открытие вспомогательной формы из модуля документа:

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


&НаКлиенте
Процедура ВвестиСведенияОСобственности(Команда)
    ПараметрыФормы = Новый Структура;
    // Передаем необходимые исходные данные во вспомогательную форму
    ПараметрыФормы.Вставить("Жилец", Элементы.Главная.ТекущиеДанные.Проживающий);
    
    ОповещениеЗавершения = Новый ОписаниеОповещения("ПослеВводаСведенийОСобственности", ЭтотОбъект);
    
    ОткрытьФорму("ОбщаяФорма.ВводСведенийОСобственности", ПараметрыФормы, ЭтотОбъект, , , , ОповещениеЗавершения);
КонецПроцедуры

&НаКлиенте
Процедура ПослеВводаСведенийОСобственности(Результат, ДополнительныеПараметры) Экспорт
    Если ТипЗнч(Результат) = Тип("Структура") Тогда
        // Мы получили чистые данные напрямую без использования Временного Хранилища!
        ЗаписатьСведенияОСобственностиНаСервере(Результат);
    КонецЕсли;
КонецПроцедуры

2. Возврат данных из вспомогательной общей формы:

Во вспомогательной форме нам вообще больше не требуется создавать временное хранилище и засорять серверные ресурсы. При закрытии формы мы передаем сформированную структуру прямо в метод Закрыть().


&НаКлиенте
Процедура ЗаписатьИЗакрыть(Команда)
    СтруктураРезультата = Новый Структура;
    СтруктураРезультата.Вставить("Жилец", Объект.Жилец);
    СтруктураРезультата.Вставить("Собственник", Объект.Собственник);
    СтруктураРезультата.Вставить("ОснованиеПолученияВСобственность", Объект.ОснованиеПолученияВСобственность);
    СтруктураРезультата.Вставить("ВидДокумента", Объект.ВидДокумента);
    СтруктураРезультата.Вставить("НомерДокумента", Объект.НомерДокумента);
    СтруктураРезультата.Вставить("ДатаДокумента", Объект.ДатаДокумента);
    
    // Закрываем форму и автоматически передаем структуру в callback-процедуру родителя
    Закрыть(СтруктураРезультата);
КонецПроцедуры

Преимущества паттерна ОписаниеОповещения:

← На главную