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