При разработке механизмов многопоточности в 1С:Предприятии 8.3 программисты часто сталкиваются с ситуацией, когда данные, переданные из фонового задания через временное хранилище, оказываются недоступны в основном сеансе. Особенно остро эта проблема проявляется в клиент-серверном варианте работы, в то время как в файловой базе всё работает корректно с первого раза. Проанализируем, почему так происходит, и как правильно организовать получение результата выполнения фонового задания.
Разберем ситуацию, описанную разработчиками: при первом запуске фонового задания система возвращает Неопределено, а при повторном — корректное значение. Выясним причину такого поведения. В клиент-серверном режиме фоновое задание выполняется в рамках отдельного сеанса на сервере приложений. Когда задание завершает свою работу и записывает данные во временное хранилище, эти данные должны быть синхронизированы с основным сеансом пользователя.
Проблема «первого раза» и случайных пропусков данных связана с микроскопической задержкой (так называемый race condition) между моментом, когда кластер регистрирует статус задания как Завершено, и физическим завершением процесса репликации данных временного хранилища между сеансами. В файловом варианте сеанс фактически один (или доступ к памяти прямой), поэтому данные видны мгновенно. В клиент-серверном режиме требуется время на «прокачку» данных из сеанса фонового задания в сеанс-владелец.
Рассмотрим пример кода, который часто приводит к подобным ошибкам в обычных формах:
// Модуль обработки (Обычные формы)
Перем Ключ, АдресРезультата;
Процедура КнопкаВыполнитьНажатие(Кнопка)
Ключ = Новый УникальныйИдентификатор;
// Ошибка: использование нового UUID вместо идентификатора формы
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, Новый УникальныйИдентификатор);
Параметры = Новый Массив;
Параметры.Добавить(АдресРезультата);
ФоновыеЗадания.Выполнить("Сервер.Сформировать", Параметры, Ключ, "Задание");
ПодключитьОбработчикОжидания("ПроверкаГотовности", 0.3, Истина);
КонецПроцедуры
Процедура ПроверкаГотовности() Экспорт
ФильтрОтбора = Новый Структура("Наименование, Ключ, Состояние", "Задание", Ключ, СостояниеФоновогоЗадания.Завершено);
МассивЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(ФильтрОтбора);
Если МассивЗаданий.Количество() > 0 Тогда
// Здесь может вернуться Неопределено, так как данные еще не долетели
Значение = ПолучитьИзВременногоХранилища(АдресРезультата);
Сообщить("Результат=" + СокрЛП(Значение));
Иначе
ПодключитьОбработчикОжидания("ПроверкаГотовности", 0.4, Истина);
КонецЕсли;
КонецПроцедуры
Одной из ключевых ошибок является использование Новый УникальныйИдентификатор() в качестве второго параметра функции ПоместитьВоВременноеХранилище. Согласно документации 1С, для того чтобы данные были доступны в форме в течение всего времени её жизни, в качестве владельца необходимо передавать идентификатор самой формы. Это гарантирует, что менеджер временного хранилища будет знать, какому сеансу и какому объекту принадлежат данные.
Заменим инициализацию адреса на следующий код:
// В обычном приложении у формы есть свойство УникальныйИдентификатор
АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, ЭтаФорма.УникальныйИдентификатор);
Проанализируем метод ФоновыеЗадания.ПолучитьФоновыеЗадания(). Если фильтровать только по имени и ключу, система может вернуть массив, содержащий старые, уже завершенные задания с таким же ключом (если обработка запускалась ранее). В этом случае код может попытаться прочитать данные из хранилища старого задания, которые уже были удалены платформой по истечении времени жизни.
Правильнее всего использовать уникальный идентификатор конкретного запущенного задания:
// В модуле формы
Перем ИдентификаторЗадания;
Процедура КнопкаВыполнитьНажатие(Кнопка)
// ... подготовка параметров ...
Задание = ФоновыеЗадания.Выполнить("Сервер.Сформировать", Параметры, Ключ, "Задание");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;
ПодключитьОбработчикОжидания("ПроверкаГотовности", 0.5, Истина);
КонецПроцедуры
Процедура ПроверкаГотовности() Экспорт
Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
Если Задание <> Неопределено И Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
Значение = ПолучитьИзВременногоХранилища(АдресРезультата);
Если Значение = Неопределено Тогда
// Если статус "Завершен", но данных нет - подождем еще немного (синхронизация)
ПодключитьОбработчикОжидания("ПроверкаГотовности", 0.2, Истина);
Возврат;
КонецЕсли;
// Работа с результатом
КонецЕсли;
КонецПроцедуры
Если логика работы позволяет на короткое время заблокировать интерфейс или если вызов происходит в серверном контексте, наиболее стабильным способом является использование метода ФоновыеЗадания.ОжидатьЗавершения(). Платформа 1С при выходе из этого метода гарантированно выполняет синхронизацию всех связанных данных временного хранилища.
Посмотрим на пример реализации:
МассивЗаданий = Новый Массив;
МассивЗаданий.Добавить(НужноеФоновоеЗадание);
Попытка
ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий, 60); // Таймаут 60 секунд
Результат = ПолучитьИзВременногоХранилища(АдресРезультата);
Исключение
Сообщить("Задание не завершилось в отведенное время или произошло аварийное завершение.");
КонецПопытки;
Для минимизации ошибок при работе с фоновыми заданиями в клиент-серверном режиме придерживайтесь следующих правил:
Завершено. Если данные еще не доступны, сделайте паузу в 0.1–0.2 секунды и повторите попытку получения.Таким образом, мы выяснили, что "глюк" платформы на самом деле является архитектурной особенностью синхронизации данных между серверными процессами. Правильное управление жизненным циклом временного хранилища и корректная обработка статусов фоновых заданий позволяют полностью избежать этой проблемы.