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