При разработке на платформе «1С:Предприятие» программисты часто сталкиваются с поведением системы, которое кажется нелогичным на первый взгляд. Одной из таких загадок является работа с методом ПолучитьИзВременногоХранилища. Представим ситуацию: вы поместили структуру в хранилище, затем получили её, изменили одно поле и, не вызывая команду ПоместитьВоВременноеХранилище повторно, обнаруживаете, что в хранилище уже лежат обновленные данные. Давайте подробно разберем, почему так происходит, заглянем во «внутреннюю кухню» платформы и научимся правильно управлять этим процессом.
Рассмотрим пример кода, который вызывает недоумение у многих разработчиков. Логика кода кажется прозрачной: мы создаем объект, сохраняем его «слепок» и ожидаем, что этот слепок останется неизменным, пока мы явно его не перепишем.
&НаСервере
Процедура ТестНаСервере()
// 1. Создаем структуру и кладем ее в хранилище
Данные = Новый Структура("Параметр", 1);
АдресХранилища = ПоместитьВоВременноеХранилище(Данные, ЭтаФорма.УникальныйИдентификатор);
// 2. Извлекаем данные
СтруктураДанных = ПолучитьИзВременногоХранилища(АдресХранилища); // Результат: "Параметр" = 1
// 3. Модифицируем локальную переменную
СтруктураДанных.Вставить("Параметр", 2);
// 4. Снова извлекаем данные по тому же адресу
НоваяСтруктура = ПолучитьИзВременногоХранилища(АдресХранилища);
// Ожидаем увидеть "Параметр" = 1, но получаем 2!
Сообщить(НоваяСтруктура.Параметр);
КонецПроцедуры
Проанализируем этот фрагмент. С точки зрения классического программирования, временное хранилище должно работать как база данных или файловая система: записали — прочитали копию. Однако в 1С при работе в контексте одного серверного вызова это правило нарушается. Выясним причину.
Главная причина кроется в механизме оптимизации производительности платформы. Давайте разберем по шагам, что делает сервер 1С, когда мы вызываем функции работы с хранилищем:
Когда мы вызываем ПоместитьВоВременноеХранилище, платформа понимает, что данные могут понадобиться в этом же сеансе или даже в этом же текущем вызове. Сериализация (превращение объекта 1С в поток байтов) — это «дорогая» операция для процессора и памяти. Если мы находимся внутри **одного и того же серверного вызова**, платформа просто запоминает указатель на объект в оперативной памяти процесса rphost.
Следовательно, когда вы вызываете ПолучитьИзВременногоХранилища в той же процедуре (или в цепочке вызванных из неё процедур), платформа не тратит время на десериализацию. Она просто отдает вам **ссылку на тот же самый объект**, который все еще живет в памяти сервера. Таким образом, переменные Данные, СтруктураДанных и НоваяСтруктура в нашем примере указывают на одну и ту же область памяти. Изменяя одну, мы видим изменения во всех остальных.
Важно понимать, что такое поведение характерно не для всех типов данных. Проанализируем ситуацию с точки зрения типов:
Структура, Соответствие, Массив, ТаблицаЗначений, ДеревоЗначений. Именно с ними проявляется эффект «самопроизвольного» изменения, так как платформа передает ссылку на существующий экземпляр коллекции.Число, Строка, Булево, Дата. Здесь вы не заметите подвоха. Даже если платформа передаст ссылку, любое «изменение» такой переменной (например, А = А + 1) на самом деле создает новое значение в памяти, не меняя старое.Рассмотрим условия, при которых 1С все-таки выполнит честное копирование данных, и вы получите независимый объект:
&НаКлиенте), контекст текущих серверных объектов уничтожается. При следующем обращении к серверу платформа будет вынуждена физически прочитать данные из временного хранилища и создать новый объект.Если вам нужно получить данные из хранилища, изменить их для каких-то вычислений, но сохранить оригинал внутри хранилища нетронутым (в рамках одного вызова), используйте явное копирование. Посмотрим на пример:
СтруктураДанных = ПолучитьИзВременногоХранилища(АдресХранилища).Скопировать();
СтруктураДанных.Параметр = 2; // Теперь это изменит только копию
Важное предостережение: Метод Скопировать() для структур и массивов выполняет так называемое «поверхностное копирование». Если внутри вашей структуры лежит другая структура или массив, то вложенный объект все равно останется ссылкой на оригинал. Для глубокого копирования сложных деревьев данных может потребоваться обход коллекции или использование сериализации через ЗначениеВСтрокуВнутр (хотя это крайняя мера).
Проанализируем лучший способ работы с хранилищем, который убережет вас от ошибок при будущем рефакторинге кода. Даже если вы знаете об эффекте ссылки, всегда придерживайтесь правила: изменил — положи обратно.
// Получаем
Данные = ПолучитьИзВременногоХранилища(АдресХранилища);
// Модифицируем
Данные.Вставить("Статус", "Обработано");
// Явно перезаписываем, даже если работаем в одном вызове
ПоместитьВоВременноеХранилище(Данные, АдресХранилища);
Почему это важно? Представьте, что завтра вы решите разделить вашу большую серверную процедуру на две разные, которые будут вызываться с клиента последовательно. Если вы полагались на «эффект ссылки», ваш код сломается, так как между вызовами ссылка перестанет работать, и данные во втором вызове окажутся старыми. Явное использование ПоместитьВоВременноеХранилище гарантирует предсказуемость кода.
Выясним, чем чревато игнорирование этих правил. Поскольку временное хранилище живет в оперативной памяти процесса rphost, бездумное помещение огромных таблиц значений может привести к дефициту памяти на сервере.
С другой стороны, знание об оптимизации «по ссылке» позволяет вам экономить ресурсы внутри одного тяжелого процесса обработки данных. Вы можете передавать адрес хранилища между процедурами вместо того, чтобы передавать саму огромную структуру через параметры, что уменьшит нагрузку на стек вызовов.
Работа с ВременнымХранилищем в 1С требует понимания контекста исполнения. Помните следующие правила:
Скопировать().ПоместитьВоВременноеХранилище после изменения данных для обеспечения универсальности кода.Применяя эти знания, мы делаем наш код более надежным и защищенным от трудноуловимых багов, связанных с неожиданным изменением данных в памяти сервера. При необходимости можно воспользоваться готовым шаблоном для запуска фоновых заданий и получения их результатов через временное хранилище.