Как программно определить, кто заблокировал объект в 1С?

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

Часто при разработке массовых обработок проведения или изменения документов мы сталкиваемся с ситуацией: код работает в транзакции, и внезапно алгоритм «спотыкается» о заблокированный объект. Кто-то из пользователей открыл документ, и наша обработка не может его изменить. Возникает закономерный вопрос: как программно и быстро выяснить, кто именно наложил блокировку, чтобы вывести понятное сообщение или записать информацию в лог?

Рассмотрим несколько подходов к решению этой задачи, проанализируем примеры кода и разберем важные нюансы работы с блокировками в 1С.

Способ 1. Использование исключений и парсинг текста ошибки

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

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


// Функция ОбъектЗаблокирован
// Параметры: Ссылка - Любая ссылка
// Возвращаемое значение: структура с двумя ключами:
// Заблокировано - Истина, если объект заблокирован, ложь если нет.
// ОписаниеПроверки - Строка содержащая в себе пользователя, сеанс и время начала работы.

Функция ОбъектЗаблокирован(Ссылка) Экспорт
    Попытка
        ЗаблокироватьДанныеДляРедактирования(Ссылка, Ссылка.ВерсияДанных);
        РазблокироватьДанныеДляРедактирования(Ссылка);
        Результат = "";
    Исключение
        Результат = ОписаниеОшибки();
    КонецПопытки;
    
    РезультатПроверки = Новый Структура;
    Если Результат = "" Тогда
        РезультатПроверки.Вставить("Заблокировано", Ложь);
        РезультатПроверки.Вставить("ОписаниеПроверки", "Не заблокировано.");
    Иначе
        // Парсим строку ошибки, разделяя ее по запятым
        МассивСтрок = СтрРазделить(СтрПолучитьСтроку(Результат, 2), ",");
        РезультатПроверки.Вставить("Заблокировано", Истина);
        РезультатПроверки.Вставить("ОписаниеПроверки", "Заблокировано: " + МассивСтрок[1] + ", " + МассивСтрок[2] + ", " + МассивСтрок[3]);
    КонецЕсли;
    
    Возврат РезультатПроверки;
КонецФункции

Важное замечание: При перехвате ошибки нужно использовать именно функцию ОписаниеОшибки(). Функция ИнформацияОбОшибке() в некоторых случаях (особенно при вызовах без контекста) может возвращать пустой результат.

Подводные камни первого способа

Проанализируем недостатки данного подхода, о которых нужно помнить:

  1. Проблема локализации: Текст ошибки, возвращаемый платформой, зависит от языка интерфейса пользователя. Если у одного пользователя русский интерфейс («Объект уже заблокирован...»), а у другого английский («Object already locked...»), жесткая привязка к индексам массива после СтрРазделить может отработать некорректно. Рекомендуется использовать регулярные выражения или искать подстроки вроде «сеанс:» или «session:».
  2. Работа внутри транзакции: Если вызов ЗаблокироватьДанныеДляРедактирования происходит внутри уже открытой транзакции (например, при проведении) и падает в исключение, транзакция помечается как невосстановимая. Последующие попытки записи вызовут ошибку В данной транзакции уже происходили ошибки!. Поэтому такие проверки лучше выполнять до начала основной транзакции.

Способ 2. Использование возможностей БСП (Библиотеки стандартных подсистем)

Если вы работаете в современной типовой конфигурации, не стоит изобретать велосипед. Разберем готовые инструменты БСП.

В БСП реализован программный интерфейс, который делает проверку более надежной:

Как обойти проблему "сломанной" транзакции?

Если вам критически важно проверить блокировку, находясь внутри сложного алгоритма, и при этом не прервать текущую транзакцию, используем метод фонового задания.

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

Способ 3. Программный доступ через сервер администрирования

Посмотрим на более продвинутый вариант, если нам нужно получить информацию о блокировках «молча», вообще не обращаясь к самому объекту. Это можно сделать через COM-соединение или утилиту администрирования:

Мы можем использовать объект МенеджерКластера. Метод ПолучитьБлокировкиБазыДанных() возвращает массив объектов со всеми текущими блокировками. В этом массиве можно найти ID сеанса и пространство блокировки, что позволяет точно определить "виновника" без генерации исключений в коде. Если блокировку держит зависшее подключение, для решения проблемы подойдёт инструмент для автоматического удаления спящих сеансов.

Различие типов блокировок: Пессимистические и Транзакционные

Крайне важно понимать, какую именно блокировку мы пытаемся отловить.

Пессимистическая блокировка устанавливается пользователем при открытии формы документа или разработчиком через метод ЗаблокироватьДанныеДляРедактирования. Именно ее мы ищем всеми вышеописанными способами.

Транзакционная (управляемая) блокировка накладывается самой системой (или разработчиком через объект БлокировкаДанных) в момент записи данных. Если обработка зависла на 20 секунд и затем упала с ошибкой таймаута — это транзакционная блокировка. Метод ЗаблокироватьДанныеДляРедактирования здесь не поможет.

Для анализа транзакционных блокировок и взаимных блокировок (deadlocks) необходимо использовать Технологический журнал платформы 1С. Настраивается сбор событий TLOCK (установка блокировки) и TTIMEOUT (превышение времени ожидания). В событии TTIMEOUT система четко укажет номер сеанса, который держал ресурс и не позволил вашей обработке его изменить.

← На главную