Как заблокировать документ от программного изменения фоновым заданием при открытой форме

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе 1С:Предприятие 8.3 часто возникает классическая коллизия: менеджер открывает документ и начинает вносить в него изменения, а в это же время регламентное или фоновое задание пытается обновить данные в этом же объекте. Если фоновое задание запишет документ раньше менеджера, то при попытке записи из формы пользователь получит критическую ошибку "Объект изменен или удален". Это происходит из-за механизма оптимистической блокировки. При реализации механизмов, призванных автоматизировать регулярный обмен данными между базами по расписанию, подобные коллизии следует исключать на уровне программного кода.

Для решения этой проблемы необходимо реализовать механизм, при котором фоновое задание будет «понимать», что документ в данный момент занят пользователем, и откладывать или пропускать его обработку. Разберем подробно, как это сделать правильно.

Понимание механизмов блокировки: почему стандартные методы не всегда работают

Прежде чем перейти к коду, проанализируем ситуацию. Многие начинающие разработчики пытаются использовать метод Заблокировать() у объекта или проверять свойство Заблокирован(). Однако стоит помнить, что метод Заблокировать() устанавливает блокировку в рамках текущего сеанса. Фоновое задание выполняется в отдельном сеансе, поэтому блокировка, установленная менеджером через ДокументОбъект.Заблокировать(), для фонового задания не будет видна. То же самое касается и проверки: ДокументОбъект.Заблокирован() проверяет состояние объекта в памяти конкретного сеанса, а не запись в базе данных.

Для взаимодействия разных сеансов (пользовательского и фонового) используется механизм пессимистической блокировки через глобальный метод ЗаблокироватьДанныеДляРедактирования(). Этот метод создает запись в специальной системной таблице на сервере 1С, которую могут видеть все остальные сеансы. Разобраться в тонкостях их поведения поможет руководство по пессимистическим и оптимистическим объектным блокировкам.

Шаг 1. Настройка управляемой формы документа

Первым делом нам нужно заявить системе, что как только пользователь открыл форму документа, этот объект должен быть защищен от вмешательства извне. Мы будем использовать событие ПриСозданииНаСервере или ПриЧтенииНаСервере.

Важный нюанс: при установке блокировки крайне рекомендуется передавать УникальныйИдентификатор формы. Это гарантирует, что блокировка будет автоматически снята платформой сразу после закрытия формы пользователем, даже если работа клиента завершится аварийно.

Рассмотрим пример кода в модуле формы:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Проверяем, что документ уже существует в базе (не новый)
    Если Не Объект.Ссылка.Пустая() Тогда
        Попытка
            // Устанавливаем пессимистическую блокировку, привязанную к идентификатору формы
            ЗаблокироватьДанныеДляРедактирования(Объект.Ссылка, , ЭтаФорма.УникальныйИдентификатор);
        Исключение
            // Если документ уже кем-то заблокирован, можно вывести сообщение или просто проигнорировать
            // В данном случае форма все равно откроется, но менеджер увидит предупреждение при записи
        КонецПопытки;
    КонецЕсли;
    
КонецПроцедуры

Обратите внимание, что использование ЗаблокироватьДанныеДляРедактирования без второго и третьего параметра привяжет блокировку к сеансу пользователя до его завершения, что может быть неудобно, если пользователь просто закроет форму, но не выйдет из программы.

Шаг 2. Реализация проверки в фоновом задании

Теперь рассмотрим код фонового задания. Чтобы пессимистическая блокировка «сработала», программный код обязательно должен явно попытаться заблокировать данные перед их модификацией. Метод Записать() сам по себе не проверяет наличие интерактивных блокировок (он проверяет только транзакционные блокировки базы данных). При проектировании таких алгоритмов будет полезен шаблон для управления фоновыми заданиями и отслеживания их прогресса.

Выясним причину, по которой фоновое задание часто игнорирует блокировку: разработчики просто вызывают ПолучитьОбъект() и Записать(). Вместо этого алгоритм в фоновом задании должен выглядеть следующим образом:


Процедура ИзменитьДокументИзФона(СсылкаНаДокумент)
    
    Попытка
        // Пытаемся заблокировать документ перед редактированием
        ЗаблокироватьДанныеДляРедактирования(СсылкаНаДокумент);
        
        // Если блокировка прошла успешно, получаем объект и меняем данные
        ДокОбъект = СсылкаНаДокумент.ПолучитьОбъект();
        ДокОбъект.Комментарий = ДокОбъект.Комментарий + " (Обновлено фоном)";
        ДокОбъект.Записать(РежимЗаписиДокумента.Запись);
        
        // Обязательно снимаем блокировку после записи, чтобы не ждать конца сеанса фонового задания
        РазблокироватьДанныеДляРедактирования(СсылкаНаДокумент);
        
    Исключение
        // Сюда мы попадем, если метод ЗаблокироватьДанныеДляРедактирования вызвал ошибку.
        // Это означает, что документ открыт у пользователя (или другого процесса).
        ЗаписьЖурналаРегистрации("ФоновоеЗадание", УровеньЖурналаРегистрации.Предупреждение, , , 
            "Документ " + СсылкаНаДокумент + " заблокирован пользователем. Пропускаем обработку.");
    КонецПопытки;
    
КонецПроцедуры

Шаг 3. Использование управляемых блокировок (DataLock) для критических операций

Если задача требует стопроцентной гарантии того, что данные не будут изменены во время выполнения транзакции, стоит проанализировать использование объекта БлокировкаДанных. Это более «тяжелый» и жесткий механизм, работающий на уровне СУБД или менеджера блокировок 1С.

В отличие от ЗаблокироватьДанныеДляРедактирования, управляемая блокировка заставит другой процесс ждать освобождения ресурса (в течение времени ожидания блокировки), а не сразу выбрасывать исключение.

Посмотрим на пример использования в фоновом задании:


Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.Событие");
ЭлементБлокировки.УстановитьЗначение("Ссылка", СсылкаНаДокумент);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;

НачатьТранзакцию();
Попытка
    Блокировка.Заблокировать();
    
    ДокОбъект = СсылкаНаДокумент.ПолучитьОбъект();
    // ... внесение изменений ...
    ДокОбъект.Записать();
    
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    // Обработка ошибки блокировки
КонецПопытки;

Рекомендации по улучшению взаимодействия

Чтобы сделать работу пользователя максимально комфортной, рассмотрим дополнительные шаги:

  1. Оповещение в форме: Если фоновое задание все же изменило документ (например, когда форма была открыта просто для просмотра без блокировки), менеджер узнает об этом только при попытке записи. Чтобы избежать этого, можно настроить ПодключитьОбработчикОжидания в форме, который раз в минуту будет проверять версию объекта в базе.
  2. Анализ сеансов: В некоторых случаях (сообщение 16 в обсуждении) предлагают проверять наличие активных сеансов фоновых заданий через функцию ПолучитьСеансыИнформационнойБазы(). Это довольно ресурсоемкая операция, и мы рекомендуем использовать ее только в исключительных случаях, когда стандартные механизмы блокировок не применимы.
  3. Использование признака в объекте: Если бизнес-логика сложная, можно добавить в документ реквизит РедактируетсяФоновымЗаданием (Булево). Фоновое задание устанавливает его в Истина при начале долгой обработки и в Ложь в конце. В форме документа при открытии можно проверять этот флаг и блокировать форму целиком для редактирования (свойство ТолькоПросмотр).

Подведем итог: для корректного решения задачи блокировки от программного изменения необходимо использовать связку из установки пессимистической блокировки в форме (ЗаблокироватьДанныеДляРедактирования с передачей УникальныйИдентификатор) и обязательной проверки этой же блокировки в коде фонового задания через конструкцию Попытка...Исключение. Это обеспечит стабильную работу системы и отсутствие конфликтов при записи данных.

← На главную