Почему переменные модуля очищаются при серверных вызовах в 1С и как правильно хранить данные?

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

Рассмотрим очень распространенную ситуацию, с которой сталкиваются многие разработчики при переходе на управляемые формы 1С. Вы объявляете переменную в модуле формы с директивой &НаСервере, инициализируете ее (например, загружаете в нее таблицу значений), а при следующем вызове с клиента обнаруживаете, что она равна Неопределено. Давайте разберем по шагам, почему это происходит, проанализируем типичные ошибки и выясним правильные способы решения этой задачи.

Анализ исходной проблемы

Посмотрим на пример кода, который часто пытаются реализовать разработчики:


&НаСервере
Перем ТаблицаСпр;

&НаСервере
Процедура ЗаполнитьТаблицаСпр()
    ...
    ТаблицаСпр = Запрос.Выполнить().Выгрузить();
КонецПроцедуры

&НаСервере
Функция НайтиСпрПоКодуРекурсивно(Код, КодРодитель)
    ...
    Строчки = ТаблицаСпр.НайтиСтроки(Новый Структура("Код, Родитель"), Код, КодРодитель);
    Возврат Строчки[0].Ссылка;     
КонецФункции

&НаКлиенте
Процедура Сформировать()
    ЗаполнитьТаблицаСпр();
    Для каждого Строчка из Таблица Цикл
        СправочникСсылочка = НайтиСпрПоКодуРекурсивно(Строчка.Код, Строчка.КодРодитель);
    КонецЦикла;
КонецПроцедуры

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

Почему серверные переменные не сохраняют значения?

Разберем архитектурные принципы платформы. Серверные вызовы в 1С работают по принципу stateless (без сохранения состояния). Это означает, что серверная часть формы инициализируется заново при каждом обращении с клиента, а после завершения серверного вызова все локальные переменные модуля безвозвратно уничтожаются.

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

Для чего же тогда вообще нужны переменные модуля &НаСервере? Они используются для протаскивания приватных данных между различными серверными методами формы исключительно на протяжении одного серверного вызова.

Главная архитектурная ошибка: вызов сервера в цикле

Проанализируем клиентскую процедуру из примера выше. Самая грубая ошибка здесь — это вызов серверной функции внутри клиентского цикла Для каждого Строчка из Таблица Цикл. Каждый клиент-серверный вызов несет огромные накладные расходы. Платформа сериализует контекст формы, передает его по сети, десериализует на сервере, выполняет код, а затем делает то же самое в обратном порядке.

Правильное решение №1: Перенос логики на сервер

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

Рассмотрим исправленный вариант кода:


&НаСервере
Процедура ОбработатьДанныеНаСервере(МассивВходныхДанных)
    // 1. Единоразово получаем справочник в таблицу значений
    Запрос = Новый Запрос("ВЫБРАТЬ Код, Родитель.Код КАК КодРодитель, Ссылка ИЗ Справочник.Номенклатура");
    ТаблицаСпр = Запрос.Выполнить().Выгрузить();
    
    // 2. Выполняем цикл обработки прямо здесь, на сервере
    Для каждого Элемент Данных Из МассивВходныхДанных Цикл
        // Ищем строки в ТаблицаСпр и выполняем рекурсию без клиент-серверных вызовов
        ...
    КонецЦикла;
КонецПроцедуры

&НаКлиенте
Процедура Сформировать()
    // Собираем необходимые данные на клиенте
    МассивДанных = Новый Массив;
    ... 
    // Делаем ОДИН вызов сервера
    ОбработатьДанныеНаСервере(МассивДанных);
КонецПроцедуры

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

Как хранить данные между вызовами, если это действительно необходимо?

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

Правильное решение №2: Использование Временного хранилища

Самый корректный способ сохранить таблицу значений или любой другой несериализуемый на клиенте объект — поместить его во временное хранилище. Для этого применяется метод ПоместитьВоВременноеХранилище.

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


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

&НаСервере
Процедура ПрочитатьТаблицуИзХранилища()
    // Восстанавливаем таблицу при следующем серверном вызове
    ТаблицаСпр = ПолучитьИзВременногоХранилища(АдресТаблицыВХранилище);
    // Работаем с данными...
КонецПроцедуры

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

Почему не стоит использовать реквизиты формы для больших таблиц?

Иногда программисты решают проблему добавлением реквизита формы с типом ТаблицаЗначений. Да, данные в этом случае сохраняются между вызовами. Однако платформа 1С устроена так, что данные всех реквизитов формы постоянно синхронизируются между клиентом и сервером (в виде объекта ДанныеФормыКоллекция).

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

Можно ли использовать Менеджер Временных Таблиц (МВТ)?

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

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

← На главную