При переходе на платформу 1С:Предприятие версии 8.3.12 и выше разработчики конфигураций на обычных формах (таких как УПП 1.3, КА 1.1, УТ 10.3) столкнулись с неприятной проблемой: привычный механизм передачи данных из фонового задания обратно в родительский сеанс через временное хранилище перестал работать. При попытке получить данные из хранилища после завершения фонового задания метод ПолучитьИзВременногоХранилища возвращает значение Неопределено.
В этой статье мы подробно разберем, почему изменилось поведение платформы, проанализируем причины этой ошибки и рассмотрим четыре надежных альтернативных способа передачи результатов работы фонового задания обратно на клиент в обычном приложении.
Временное хранилище по своей природе жестко изолировано в рамках одного сеанса. Фоновое задание всегда запускается в собственном, отдельном сеансе. Единственным штатным исключением, позволяющим передать данные из фонового задания в вызвавший его родительский сеанс, является использование адреса временного хранилища, который жестко связан с уникальным идентификатором формы.
До версии 8.3.12 в обычных формах разработчики прибегали к хитрости (недокументированной особенности платформы): они генерировали случайный идентификатор с помощью функции Новый УникальныйИдентификатор и регистрировали временное хранилище по этому адресу. Рассмотрим типичный пример кода, который отлично работал на версиях 8.3.8 и 8.3.10, но полностью перестал функционировать в 8.3.12:
// Модуль обычной формы
Перем Ключ;
Перем АдресРезультата;
Процедура Выполнить()
// В ОФ нет УИД формы, поэтому генерировали случайный идентификатор
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, Новый УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресРезультата);
Ключ = Новый УникальныйИдентификатор;
ФоновыеЗадания.Выполнить("ПолныеПрава.Сформировать", Параметры, Ключ, "ФоновыйОтчет");
ПодключитьОбработчикОжидания("ПроверкаГотовности", 1);
КонецПроцедуры
Процедура ПроверкаГотовности() Экспорт
ФильтрОтбора = Новый Структура("Наименование, Ключ, Состояние", "ФоновыйОтчет", Ключ, СостояниеФоновогоЗадания.Завершено);
МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ФильтрОтбора);
Если МассивЗаданий.Количество() > 0 Тогда
// На платформе 8.3.12 этот метод теперь ВСЕГДА возвращает Неопределено
Значение = ПолучитьИзВременногоХранилища(АдресРезультата);
Сообщить(Значение);
ОтключитьОбработчикОжидания("ПроверкаГотовности");
КонецЕсли;
КонецПроцедуры
На стороне серверного общего модуля выполнялась запись:
// Серверный модуль ПолныеПрава
Процедура Сформировать(АдресРезультата) Экспорт
Результат = "Важные данные отчета";
ПоместитьВоВременноеХранилище(Результат, АдресРезультата);
КонецПроцедуры
Почему это происходило? Начиная с версии платформы 8.3.12, разработчики 1С усилили безопасность и изоляцию данных между сеансами. Теперь платформа выполняет строгую валидацию адреса. Если переданный уникальный идентификатор не принадлежит реально существующей и активной в данный момент управляемой форме родительского сеанса, запись данных из фонового сеанса блокируется на уровне ядра СУБД. Режим совместимости конфигурации эту проблему не решает.
Поскольку в чистых обычных формах концепция управляемой формы и ее серверного идентификатора отсутствует, старая схема перестала работать. Тем не менее решение есть. Разберем рабочие обходные пути.
---
Это самый изящный и полностью штатный способ. Если в вашей конфигурации в свойствах проекта разрешено использование управляемых форм (включен флаг "Использовать управляемые формы в обычном приложении"), мы можем программно создать управляемую форму прямо из кода обычной формы, забрать ее реальный УникальныйИдентификатор и привязать адрес временного хранилища к ней.
Разберем реализацию этого метода по шагам:
ВспомогательнаяФормаПередачиДанных.
// Модуль обычной формы
Перем Ключ;
Перем АдресРезультата;
Перем ВспомогательнаяУправляемаяФорма; // Переменная модуля для удержания формы в памяти
Процедура Выполнить()
// Программно создаем управляемую форму без открытия на экране
ВспомогательнаяУправляемаяФорма = ПолучитьФорму("ОбщаяФорма.ВспомогательнаяФормаПередачиДанных");
// Получаем легитимный УникальныйИдентификатор активной управляемой формы
УидФормы = ВспомогательнаяУправляемаяФорма.УникальныйИдентификатор;
// Помещаем пустое значение, связав адрес с легитимным УИД
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, УидФормы);
Параметры = Новый Массив;
Параметры.Добавить(АдресРезультата);
Ключ = Новый УникальныйИдентификатор;
ФоновыеЗадания.Выполнить("ПолныеПрава.Сформировать", Параметры, Ключ, "ФоновыйОтчет");
ПодключитьОбработчикОжидания("ПроверкаГотовности", 1);
КонецПроцедуры
Процедура ПроверкаГотовности() Экспорт
ФильтрОтбора = Новый Структура("Наименование, Ключ, Состояние", "ФоновыйОтчет", Ключ, СостояниеФоновогоЗадания.Завершено);
МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ФильтрОтбора);
Если МассивЗаданий.Количество() > 0 Тогда
// Теперь платформа успешно вернет результат, так как УИД зарегистрирован
Значение = ПолучитьИзВременногоХранилища(АдресРезультата);
Сообщить("Результат успешно получен: " + Значение);
// Обязательно освобождаем переменную, чтобы уничтожить форму в памяти
ВспомогательнаяУправляемаяФорма = Неопределено;
ОтключитьОбработчикОжидания("ПроверкаГотовности");
КонецЕсли;
КонецПроцедуры
Этот метод работает со скоростью оперативной памяти, не требует дополнительных обращений к жесткому диску базы данных и является абсолютно штатным с точки зрения платформы 1С.
---
Если в конфигурации категорически запрещено использование управляемых форм, отличной альтернативой станет механизм системных сообщений. Этот способ работает исключительно в оперативной памяти и отлично подходит для передачи сериализуемых данных среднего объема (строки, XML, JSON, структуры, таблицы значений).
Рассмотрим пример передачи сформированного на сервере табличного документа обратно на клиент. На сервере сериализуем объект в XML и выведем его через механизм сообщений:
// Серверный модуль ПолныеПрава
Процедура СформироватьИПередатьЧерезСообщение() Экспорт
ТабДок = Новый ТабличныйДокумент;
// ... Логика заполнения табличного документа ...
// Сериализуем табличный документ в XML-строку
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписатьXML(ЗаписьXML, ТабДок);
СтрокаРезультата = ЗаписьXML.Закрыть();
// Передаем сериализованную строку через сообщение пользователю
ОбщегоНазначенияКлиентСервер.СообщитьПользователю(СтрокаРезультата);
КонецПроцедуры
Теперь на стороне клиента в обработчике ожидания обычной формы прочитаем отправленное сообщение:
// Модуль обычной формы в обработчике ожидания
Процедура ПроверкаГотовностиСообщений() Экспорт
// Проверяем завершение фонового задания...
// Если задание завершено успешно:
// Получаем все сообщения, сгенерированные фоновым заданием, очищая буфер
МассивСообщений = ПолучитьСообщенияПользователя(Истина);
Для Каждого Сообщение Из МассивСообщений Цикл
ТекстXML = Сообщение.Текст;
// Восстанавливаем табличный документ из строки XML
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстXML);
Попытка
ТабДок = ПрочитатьXML(ЧтениеXML);
ТабДок.Показать("Результат отчета");
Исключение
// Обрабатываем системные или штатные сообщения, не являющиеся результатом
Сообщить("Служебное сообщение: " + ТекстXML);
КонецПопытки;
КонецЦикла;
ОтключитьОбработчикОжидания("ПроверкаГотовностиСообщений");
КонецПроцедуры
Преимущество метода: Очень высокая скорость работы, так как передача идет напрямую в рамках сетевого пакета протокола обмена. Недостаток: Передача гигантских объемов текста (например, файлов размером в сотни мегабайт) может привести к повышенному потреблению оперативной памяти клиентским сеансом.
---
Если вам необходимо передавать тяжелые несериализуемые данные или файлы большого размера, рекомендуется использовать базу данных, но не через тяжелые транзакционные таблицы (регистры сведений), а через системное ХранилищеОбщихНастроек. Это позволяет избежать блокировок базы и лишнего разрастания транзакционного лога.
Посмотрим на пример реализации:
// Серверный модуль ПолныеПрава
Процедура СформироватьИСохранитьВНастройки(КлючЗадания) Экспорт
ТабличныйДокументСДанными = Новый ТабличныйДокумент;
// ... Заполнение документа ...
// Упаковываем результат в ХранилищеЗначения со сжатием
ХранилищеРезультата = Новый ХранилищеЗначения(ТабличныйДокументСДанными, Новый СжатиеДанных(9));
// Сохраняем в системную таблицу общих настроек
ХранилищеОбщихНастроек.Сохранить("РезультатыФоновыхЗаданий", КлючЗадания, ХранилищеРезультата);
КонецПроцедуры
На клиенте после завершения фонового задания считываем результат и сразу очищаем за собой таблицу, чтобы не засорять базу:
// Модуль обычной формы в обработчике ожидания после завершения ФЗ
Процедура ПолучитьРезультатИзНастроек(КлючЗадания)
// Загружаем данные из настроек
ЗаписьРезультата = ХранилищеОбщихНастроек.Загрузить("РезультатыФоновыхЗаданий", КлючЗадания);
Если ЗаписьРезультата <> Неопределено Тогда
ТабДок = ЗаписьРезультата.Получить();
// Обязательно удаляем запись из хранилища настроек СУБД за собой!
ХранилищеОбщихНастроек.Удалить("РезультатыФоновыхЗаданий", КлючЗадания);
ТабДок.Показать();
КонецЕсли;
КонецПроцедуры
---
Если в вашей конфигурации внедрена БСП (даже старых редакций для обычных форм), настоятельно рекомендуется отказаться от ручного вызова фоновых заданий через объект ФоновыеЗадания. Используйте штатный программный интерфейс общего модуля ДлительныеОперации.
Функции БСП спроектированы так, чтобы автоматически определять текущий контекст исполнения, тип приложения и версию платформы. Под капотом библиотеки уже реализованы оптимальные сценарии обмена файлами и кэширования результатов.
// Запуск длительной операции средствами БСП
ПараметрыВыполнения = Новый Массив;
РезультатВыполнения = ДлительныеОперации.ЗапуститьВыполнениеВФоне(
УникальныйИдентификатор,
"ПолныеПрава.СформироватьЭкспортныйМетод",
ПараметрыВыполнения,
"НаименованиеЗадания"
);
Такой подход гарантирует стабильность вашего кода при последующих обновлениях платформы «1С:Предприятие».
Подведем итоги: Изменение поведения платформы 8.3.12 в отношении временного хранилища — это не баг, а запланированное усиление безопасности сеансов. Для обычных форм наиболее универсальным и быстрым решением является создание скрытой управляемой формы (Решение 1) или передача сериализованных данных через сообщения (Решение 2).