В практике разработки на платформе 1С:Предприятие 8 часто встречается ситуация, когда программист создает документ, записывает его и тут же пытается отправить его на обработку в фоновое задание. Однако внутри фонового задания вместо ожидаемой ссылки на документ система возвращает «Объект не найден». Особенно странно это выглядит, когда один из нескольких документов обрабатывается успешно, а другой — нет. Проанализируем, почему так происходит, и разберем механизмы работы транзакций и сеансов, которые лежат в основе этой проблемы.
Для того чтобы понять суть ошибки, нам необходимо рассмотреть, как 1С взаимодействует с системой управления базами данных (СУБД). Любая запись данных в 1С (будь то метод Записать() или выполнение кода внутри блока НачатьТранзакцию() ... ЗафиксироватьТранзакцию()) подчиняется правилам ACID (атомарность, согласованность, изолированность, долговечность).
Разберем по шагам, что происходит в системе:
ФоновыеЗадания.Выполнить() порождает новый поток на сервере приложений 1С. Важно понимать: фоновое задание — это абсолютно другой сеанс со своим уникальным номером и отдельным соединением с базой данных.Read Committed (чтение зафиксированных данных). Это означает, что любой сеанс (в нашем случае — фоновое задание) не имеет права видеть данные, которые были изменены или созданы в другой транзакции, пока та не будет завершена командой ЗафиксироватьТранзакцию().Таким образом, когда фоновое задание пытается получить объект по переданной ссылке ДокументОбъект.Ссылка, СУБД честно отвечает, что такой записи в таблице еще нет (она скрыта для всех, кроме владельца транзакции). В результате 1С формирует битую ссылку с текстом <Объект не найден>.
В исходной ситуации мы видим, что первый документ в фоновом задании не находится, а второй — обрабатывается нормально. Это классический пример состояния гонки (race condition). Проанализируем временную шкалу событий:
Фоновое задание инициализируется не мгновенно. Платформе требуется время на запуск нового сеанса, проверку прав доступа и загрузку конфигурации в память. Пока создавалось и запускалось первое задание, транзакция в основном сеансе еще продолжалась. Но к моменту, когда инициализировалось второе фоновое задание, основной сеанс мог уже дойти до конца кода и выполнить автоматическую фиксацию транзакции. Именно поэтому второе задание «успело» увидеть данные, а первое — нет.
Выясним еще одну важную деталь. Если фоновое задание обратилось к базе данных и получило ответ, что записи не существует, платформа 1С может закэшировать этот результат на короткий промежуток времени внутри сеанса фонового задания. Даже если транзакция в основном сеансе зафиксируется через долю секунды после этого обращения, повторная попытка прочитать данные в том же фоновом задании может снова вернуть <Объект не найден> из-за обращения к объектному кэшу сеанса.
Рассмотрим популярные, но ошибочные попытки решить проблему:
ПолучитьСсылкуНового(): Этот метод просто резервирует уникальный идентификатор (GUID) в памяти. Он никак не влияет на физическую запись данных в таблицы СУБД. Ссылка будет существовать, но данных за ней в базе все равно не будет до фиксации транзакции.Ждать(1) или пустой цикл внутри фонового задания часто приводит к взаимным блокировкам (Deadlocks). Основной сеанс может ждать завершения фонового задания, а фоновое задание ждет, когда основной сеанс отпустит блокировку на таблицу документа. Процессы «замирают» навсегда.Самый надежный и правильный архитектурный подход — гарантировать, что данные уже зафиксированы в базе данных перед запуском фонового процесса. Посмотрим на пример кода, как переработать логику:
Процедура ОбработатьДокументы()
МассивСсылок = Новый Массив;
НачатьТранзакцию();
Попытка
Для Каждого Строка Из СписокДокументов Цикл
НовыйДок = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// Заполнение полей...
НовыйДок.Записать();
МассивСсылок.Добавить(НовыйДок.Ссылка);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
Возврат;
КонецПопытки;
// Запускаем фоновые задания ТОЛЬКО после успешной фиксации
Для Каждого Ссылка Из МассивСсылок Цикл
ПараметрыЗадания = Новый Массив;
ПараметрыЗадания.Добавить(Ссылка);
ФоновыеЗадания.Выполнить("МойМодуль.ПровестиДокумент", ПараметрыЗадания);
КонецЦикла;
КонецПроцедуры
Если вы работаете в контексте формы документа, запуск фонового задания следует размещать не в процедуре ПередЗаписью или ПриЗаписи (которые всегда выполняются внутри транзакции), а в событии ПослеЗаписи. На этом этапе платформа гарантирует, что данные уже физически находятся в базе и доступны всем остальным сеансам.
В ситуациях, когда фоновому заданию не нужно изменять сам объект (например, требуется только расчет или отправка уведомления), можно передать в фоновое задание не Ссылка, а Структура, содержащую все необходимые реквизиты. В этом случае фоновому заданию вообще не придется обращаться к базе данных для получения информации о документе.
Однако, как мы проанализировали ранее, если цель фонового задания — проведение документа, этот метод не сработает, так как процедура проведения обязана работать с объектом, хранящимся в БД.
Стоит отметить, что на СУБД PostgreSQL проблема «невидимости» ссылок проявляется более стабильно и жестко, чем на MS SQL Server. Это связано с механизмом MVCC (Multiversion Concurrency Control). PostgreSQL создает «снимок» данных (snapshot) для нового сеанса, и пока транзакция в первом сеансе не завершена, второй сеанс даже при повторных обращениях может не увидеть изменений. Поэтому в средах с PostgreSQL строгое соблюдение порядка «сначала фиксация — потом фоновый запуск» является критически важным.
Подведем итог: всегда разделяйте во времени процессы записи данных и процессы их параллельной обработки в других сеансах. Это избавит вас от трудноуловимых ошибок и обеспечит стабильную работу системы.