Как получить адрес временного хранилища, сформированного фоновым заданием, и передать сложные данные (например, ТаблицуЗначений) на клиент в 1С?

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

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

Суть проблемы:

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

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

Анализируем ситуацию с передачей адреса из фонового задания

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


// Пример кода в модуле сервера, который запускается фоновым заданием
// Функция, выполняющаяся в фоновом задании
Функция ВыполнитьДлительнуюОперациюФоном() Экспорт
    // ... длительные расчеты ...

    // Создаем некую большую хрень, которую нужно сохранить
    ПеременнаяСБольшимиДанными = Новый Структура;
    ПеременнаяСБольшимиДанными.Вставить("ИдентификаторПрайса", Новый УникальныйИдентификатор());
    ПеременнаяСБольшимиДанными.Вставить("ИнформацияФайл", "Данные из файла импорта");
    ПеременнаяСБольшимиДанными.Вставить("ТабличныйДокумент", Новый ТабличныйДокумент()); // Может быть и ТаблицаЗначений, но об этом позже

    // Помещаем эти данные в собственное временное хранилище фонового задания
    // Генерируем новый уникальный идентификатор для адреса
    АдресДанныхВХранилище = ПоместитьВоВременноеХранилище(ПеременнаяСБольшимиДанными, Новый УникальныйИдентификатор());

    // Формируем структуру результата, которую вернет фоновое задание
    // Эта структура сама будет помещена во временное хранилище родительского сеанса
    СтруктураРезультата = Новый Структура;
    СтруктураРезультата.Вставить("Статус", "Успех");
    СтруктураРезультата.Вставить("АдресДанныхВХранилище", АдресДанныхВХранилище); // Это и есть наш "адрес в адресе"
    // Добавляем другие простые данные, которые легко сериализуются
    СтруктураРезультата.Вставить("ИдентификаторПрайса", ПеременнаяСБольшимиДанными.ИдентификаторПрайса);
    СтруктураРезультата.Вставить("ИнформацияФайл", ПеременнаяСБольшимиДанными.ИнформацияФайл);

    Возврат СтруктураРезультата;
КонецФункции

После завершения фонового задания клиентский код вызывает обработчик завершения, который получает результат. Результат фонового задания всегда возвращается клиенту в виде адреса временного хранилища (поле Результат.АдресРезультата), в котором находится сериализованная структура результата фонового задания. Наш клиентский код попытался напрямую получить внутренний адрес и использовать его:


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

    Если Результат.Статус = "Ошибка" Тогда
        ВызватьИсключение Результат.ПодробноеПредставлениеОшибки;
    КонецЕсли;

    // Получаем структуру результата фонового задания из временного хранилища
    // Это хранилище находится в контексте родительского сеанса
    СтруктураРезультатаФона = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);    

    // Вот здесь возникает проблема:
    // Объект.АдресДанныхВХранилище = СтруктураРезультатаФона.АдресДанныхВХранилище; 
    // Эта строка не работает, потому что адрес, хоть и передан как строка, 
    // указывает на данные, помещенные в хранилище контекста фонового задания.
    // Клиентский сеанс не может напрямую получить данные по этому адресу.

    // ... дальнейшая логика, которая будет использовать данные ...
    // Объект.ИдентификаторПрайса = СтруктураРезультатаФона.ИдентификаторПрайса; 
    // Объект.ИнформацияФайл = СтруктураРезультатаФона.ИнформацияФайл; 
    // Объект.ТабличныйДокумент = СтруктураРезультатаФона.ТабличныйДокумент; // Если бы ТабличныйДокумент был в основной структуре
КонецПроцедуры

Причина, по которой Объект.АдресДанныхВХранилище = СтруктураРезультатаФона.АдресДанныхВХранилище; не работает, кроется в природе временного хранилища и сеансов 1С. Адрес временного хранилища, полученный из фонового задания, является строкой. Когда мы пытаемся использовать этот адрес в клиентском сеансе для получения данных через ПолучитьИзВременногоХранилища() (даже если бы это было на клиенте), система пытается найти данные по этому адресу в контексте текущего (клиентского) сеанса, а не в сеансе, где фоновое задание разместило эти данные. Несмотря на то, что это *логически* один и тот же адрес, *контекст* его использования имеет значение.

Эффективное решение: серверный вызов для извлечения данных

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

  1. Фоновое задание: Фоновое задание по-прежнему формирует нужные данные, помещает их во временное хранилище, генерируя для этого свой уникальный адрес. Затем этот адрес (как обычная строка) включается в структуру результата фонового задания, которая возвращается в основной сеанс.

    
    // Код фонового задания (без изменений)
    Функция ВыполнитьДлительнуюОперациюФоном() Экспорт
        // ... длительные расчеты ...
    
        ПеременнаяСБольшимиДанными = Новый Структура;
        // ... заполнение ПеременнаяСБольшимиДанными ...
    
        // Помещаем данные в хранилище, получаем адрес как строку
        АдресДанныхВХранилище = ПоместитьВоВременноеХранилище(ПеременнаяСБольшимиДанными, Новый УникальныйИдентификатор());
    
        СтруктураРезультата = Новый Структура;
        СтруктураРезультата.Вставить("Статус", "Успех");
        СтруктураРезультата.Вставить("АдресДанныхВХранилище", АдресДанныхВХранилище); 
        СтруктураРезультата.Вставить("ИдентификаторПрайса", ПеременнаяСБольшимиДанными.ИдентификаторПрайса);
        СтруктураРезультата.Вставить("ИнформацияФайл", ПеременнаяСБольшимиДанными.ИнформацияФайл);
    
        Возврат СтруктураРезультата;
    КонецФункции
    
  2. Клиентский обработчик завершения: Клиентский обработчик завершения фонового задания по-прежнему получает общий результат фонового задания (адрес его структуры во временном хранилище родительского сеанса). Он извлекает эту структуру и получает из нее строку, содержащую наш "внутренний" адрес. Ключевое изменение: вместо попытки использования адреса на клиенте, клиент вызывает серверную процедуру, передавая ей этот адрес в качестве параметра.

    
    // Клиентский обработчик завершения фонового задания
    &НаКлиенте
    Процедура ОткрытьФайлИмпорта_ПриЗавершении(Результат, ДополнительныеПараметры) Экспорт
        Если Результат = Неопределено Тогда 
            Возврат;
        КонецЕсли;
    
        Если Результат.Статус = "Ошибка" Тогда
            ВызватьИсключение Результат.ПодробноеПредставлениеОшибки;
        КонецЕсли;
    
        // Получаем структуру результата фонового задания из временного хранилища
        СтруктураРезультатаФона = ПолучитьИзВременногоХранилища(Результат.АдресРезультата);    
        // Удаляем временное хранилище для основного результата фонового задания, если оно больше не нужно
        // УдалитьИзВременногоХранилища(Результат.АдресРезультата); // Это можно сделать, если данные не нужны на клиенте
    
        // Теперь вызываем серверную процедуру для получения "больших" данных по их адресу
        // Передаем весь СтруктураРезультатаФона, так как в ней есть и простые поля, и нужный нам адрес
        ПолучитьИОбработатьДанныеСервер(СтруктураРезультатаФона);
    КонецПроцедуры
    
  3. Серверная процедура: На сервере, в контексте родительского сеанса, мы можем использовать адрес, полученный из фонового задания, для извлечения данных из временного хранилища. Это работает, потому что серверный контекст имеет полный доступ ко всем временным хранилищам, созданным в его рамках или доступным ему через механизмы передачи.

    
    // Серверная процедура, вызываемая с клиента
    &НаСервере
    Процедура ПолучитьИОбработатьДанныеСервер(СтруктураРезультатаФона)
        // Простые данные уже доступны на сервере
        Объект.ИдентификаторПрайса = СтруктураРезультатаФона.ИдентификаторПрайса; 
        Объект.ИнформацияФайл = СтруктураРезультатаФона.ИнформацияФайл; 
    
        // Получаем "большие" данные по адресу, который был сформирован в фоновом задании
        // На этом этапе платформа корректно находит данные, так как мы находимся на сервере
        ПеременнаяСБольшимиДанными = ПолучитьИзВременногоХранилища(СтруктураРезультатаФона.АдресДанныхВХранилище);
    
        // Теперь переменная ПеременнаяСБольшимиДанными содержит наши данные
        // Мы можем извлечь из нее ТабличныйДокумент или другие данные
        Объект.ТабличныйДокумент = ПеременнаяСБольшимиДанными.ТабличныйДокумент;
    
        // Очищаем временное хранилище, если данные больше не нужны
        // УдалитьИзВременногоХранилища(СтруктураРезультатаФона.АдресДанныхВХранилище);
    
        // Теперь, когда все данные получены на сервере и обработаны, 
        // мы можем обновить реквизиты формы или вызвать другие клиентские процедуры
        // ... (например, передать ТабличныйДокумент в реквизит формы)
        // Пример: если у нас есть реквизит формы "ТабличныйДокументНаФорме"
        // ЗначениеВРеквизитФормы(ПеременнаяСБольшимиДанными.ТабличныйДокумент, "ТабличныйДокументНаФорме");
        // Или если это уже реквизит объекта формы (Объект.ТабличныйДокумент) как в примере
    
        // Вызываем клиентскую процедуру для завершения работы с формой
        ЗаполнитьТаблицыВОбработкеКлиент(); // Передача управления обратно на клиент
    КонецПроцедуры
    
    &НаКлиенте
    Процедура ЗаполнитьТаблицыВОбработкеКлиент()
        ЗаполнитьТаблицыВОбработке();    
        ПолучитьСохраненныеНастройкиКолонокПрофиляСервер(); // Если это серверный вызов
        Элементы.Страницы.ТекущаяСтраница = Элементы.ИсходнаяТаблица;     
        // ...
    КонецПроцедуры
    

Важно: При таком подходе мы эффективно обходим проблему "невидимости" адреса, используя серверный контекст для доступа к данным. Адрес, переданный в структуре результата, воспринимается сервером как валидный идентификатор данных во временном хранилище.

Особенности передачи ТаблицыЗначений (ТЗ)

При попытке напрямую вернуть ТаблицуЗначений в составе структуры результата фонового задания, клиентский код может столкнуться с ошибкой XDTO сериализации:

Ошибка получения значения из временного хранилища по причине: Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа. по причине: Ошибка преобразования данных XDTO: ... Отсутствует отображение для типа '{http://v8.1c.ru/8.1/data/core}ValueTable'

Это происходит потому, что платформа 1С при передаче данных между клиентом и сервером (в частности, при десериализации результата фонового задания на клиенте) использует XDTO-сериализацию. Некоторые сложные типы данных, такие как ТаблицаЗначений (ValueTable), не имеют прямого XDTO-отображения для передачи *в таком контексте*. Интересно, что ТабличныйДокумент при этом может передаваться без проблем.

Как решить проблему с ТаблицейЗначений:

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

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

  2. В структуру результата фонового задания (которая передается на клиент) мы помещаем не саму ТаблицуЗначений, а только строковый адрес, по которому эта таблица (или содержащая ее структура) лежит во временном хранилище. Передача строки не вызывает проблем с XDTO-сериализацией.

  3. Когда клиент вызывает серверную процедуру и передает ей этот строковый адрес, серверная процедура вызывает ПолучитьИзВременногоХранилища(). Эта операция также полностью выполняется на сервере, и платформа может корректно десериализовать ТаблицуЗначений из серверного временного хранилища. Таким образом, ТаблицаЗначений никогда не покидает серверную сторону в виде, который требовал бы проблемной XDTO-сериализации для клиента.

  4. Если после получения ТаблицыЗначений на сервере нам все же нужно отобразить ее данные на клиенте, мы можем использовать стандартные механизмы:

    • Передать данные в реквизит формы типа ТаблицаЗначений с помощью ЗначениеВРеквизитФормы().
    • Напрямую заполнить динамический список или элемент управления (например, табличное поле) данными из полученной на сервере ТаблицыЗначений.

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

Дополнительные рекомендации и важные моменты

  1. Контроль жизни временного хранилища: Всегда помните об очистке временного хранилища. Данные, помещенные в него, занимают оперативную память сервера. Если данные больше не нужны, используйте УдалитьИзВременногоХранилища(Адрес). В нашем примере это можно сделать как после получения основной структуры результата фонового задания на клиенте (УдалитьИзВременногоХранилища(Результат.АдресРезультата);), так и после получения "больших" данных по внутреннему адресу на сервере (УдалитьИзВременногоХранилища(СтруктураРезультатаФона.АдресДанныхВХранилище);).

  2. Передача адреса из родительского сеанса в фоновое задание: Платформа 1С также поддерживает обратный сценарий: когда родительский сеанс создает адрес временного хранилища (помещая туда пустое значение или идентификатор формы) и передает этот адрес в параметры фонового задания. Если фоновое задание затем использует этот адрес в параметре <Адрес> метода ПоместитьВоВременноеХранилище(), то результат будет скопирован непосредственно в сеанс, из которого было запущено фоновое задание. Это позволяет фоновому заданию "наполнять" хранилище родительского сеанса. Однако данные, помещенные таким образом в фоновом сеансе, становятся недоступными в фоновом сеансе сразу после помещения. Это другой, но полезный механизм.

  3. Проверка адреса: Перед использованием адреса можно проверить его валидность с помощью функции ЭтоАдресВременногоХранилища(Адрес).

  4. Объем данных: Механизм временного хранилища поддерживает практически любые данные, доступные в платформе 1С, которые могут быть сохранены в сериализованном виде. Максимальный объем данных, помещаемых за один вызов, не должен превышать 4 Гб в сериализованном виде. Однако передача очень больших объемов данных между клиентом и сервером или даже через временное хранилище может потреблять значительный объем памяти и влиять на производительность. Для критически больших объемов данных стоит рассмотреть альтернативные подходы, например, использование временных таблиц в запросах (если база на SQL) или потоковую обработку данных.

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

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

← На главную