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