В процессе разработки в среде «1С:Предприятие 8» программисты часто сталкиваются с необходимостью управления пессимистическими объектными блокировками в ручном режиме. Это особенно актуально при работе с произвольными управляемыми формами, внешними обработками или при программной генерации интерфейса, где отсутствует стандартный основной реквизит формы типа ДокументОбъект или СправочникОбъект с установленным флагом сохраняемых данных.
Типичный сценарий: при открытии формы существующего документа накладывается блокировка, затем документ перезаписывается, и разработчик повторно вызывает блокировку, чтобы не дать другим пользователям редактировать данные при открытой форме. Однако при закрытии формы или явном вызове разблокировки выясняется, что блокировка с объекта не снялась. В результате в других сеансах при попытке открыть этот же документ возникает исключение: «Объект заблокирован». Выясним причины такого поведения платформы 1С и разберем эффективные методы решения этой проблемы.
Рассмотрим по шагам алгоритм, который приводит к зависанию блокировки в системе:
ЗаблокироватьДанныеДляРедактирования(Ссылка, , ИдентификаторФормы). Объект успешно блокируется для редактирования другими пользователями.ЗаблокироватьДанныеДляРедактирования(Ссылка, , ИдентификаторФормы) с целью продлить или подтвердить удержание блокировки в открытой форме.РазблокироватьДанныеДляРедактирования(Ссылка, ИдентификаторФормы).ЗаблокироватьДанныеДляРедактирования(Ссылка, , ИдентификаторФормыСеанса2). Платформа выбрасывает исключение о том, что объект заблокирован первым сеансом.Проанализируем глубокие системные механизмы платформы 1С, которые приводят к возникновению этой коллизии.
Пессимистические объектные блокировки в «1С:Предприятии 8» спроектированы по принципу рекурсивного счетчика (аналогично механизму reentrant lock в классическом многопоточном программировании). Это означает следующее:
Когда вы вызываете метод ЗаблокироватьДанныеДляРедактирования() для одного и того же объекта (ссылки) с одним и тем же уникальным идентификатором формы несколько раз подряд, платформа не просто игнорирует повторные вызовы или обновляет существующую блокировку. Она увеличивает внутренний счетчик захватов для данного ресурса в рамках текущего сеанса.
Соответственно, однократный вызов метода РазблокироватьДанныеДляРедактирования() лишь уменьшает этот внутренний счетчик на единицу, но не удаляет запись о блокировке из менеджера блокировок службы 1С полностью, так как счетчик все еще больше нуля. Чтобы полностью освободить ресурс, требуется вызвать разблокировку ровно столько раз, сколько раз была установлена блокировка.
Каждая запись объекта в базу данных приводит к изменению системного реквизита ВерсияДанных (в СУБД это обычно реализуется через тип данных rowversion или timestamp). Смена версии кардинально меняет поведение менеджера блокировок:
ЗаблокироватьДанныеДляРедактирования() фиксирует блокировку на состояние объекта с версией V1.В некоторых релизах платформы служба блокировок сервера 1С регистрирует это как два абсолютно разных ключа блокировки (блокировку устаревшей версии V1 и блокировку новой версии V2). При вызове стандартного метода разблокировки без указания конкретной версии платформа может некорректно сопоставить ключи в памяти, из-за чего блокировка версии V1 зависает в памяти сервера приложений как «осиротевшая».
При выполнении записи объекта внутри транзакции СУБД и сама платформа накладывают собственные системные транзакционные блокировки. Если в этот момент на объекте уже висит активная ручная пессимистическая блокировка с привязкой к конкретному идентификатору формы, менеджер блокировок может войти в конфликтное состояние. Это препятствует корректному автоматическому или ручному очищению зарегистрированных ключей блокировки после завершения транзакции.
Разберем три практических способа обхода и исправления данной проблемы в кодовой базе.
Самый простой и гарантированно работающий способ избежать зависания счетчика блокировок и конфликта версий — это принудительный сброс предыдущей блокировки перед наложением новой. Если вам необходимо обновить блокировку после записи объекта, обязательно вызовите метод разблокировки перед повторной установкой. Схема работы выглядит так:
// 1. Установка блокировки при открытии формы
ЗаблокироватьДанныеДляРедактирования(СсылкаНаДокумент, , ИдентификаторФормы);
// ... Производятся изменения данных пользователем ...
// 2. Перед записью или непосредственно после записи сбрасываем старую блокировку
РазблокироватьДанныеДляРедактирования(СсылкаНаДокумент, ИдентификаторФормы);
// 3. Производим запись объекта в базу данных
ДокументОбъект.Записать();
// 4. Устанавливаем блокировку заново для продолжения редактирования в форме
ЗаблокироватьДанныеДляРедактирования(СсылкаНаДокумент, , ИдентификаторФормы);
В этом случае счетчик блокировок гарантированно сбрасывается в ноль при вызове разблокировки, а повторный захват устанавливает значение счетчика в единицу. При последующем закрытии формы единственный вызов РазблокироватьДанныеДляРедактирования() полностью освободит документ для других пользователей.
Для того чтобы платформа 1С четко понимала, какую именно версию объекта вы пытаетесь заблокировать, и не создавала «осиротевших» ключей блокировки в памяти сервера приложений, необходимо использовать второй параметр метода ЗаблокироватьДанныеДляРедактирования(). Рассмотрим пример реализации:
// При открытии формы получаем текущую версию данных из ссылки
ТекущаяВерсия = ОбщегоНазначения.ВерсияДанныхОбъекта(СсылкаНаДокумент);
ЗаблокироватьДанныеДляРедактирования(СсылкаНаДокумент, ТекущаяВерсия, ИдентификаторФормы);
// Выполняем перезапись объекта в коде
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// ... изменение реквизитов ...
ДокументОбъект.Записать();
// Получаем новую версию данных после успешной записи документа
НоваяВерсия = ДокументОбъект.ВерсияДанных;
// Блокируем объект под новой версией данных
ЗаблокироватьДанныеДляРедактирования(СсылкаНаДокумент, НоваяВерсия, ИдентификаторФормы);
Передача конкретной версии данных позволяет службе блокировок платформы корректно обновлять существующие записи в таблице блокировок, сопоставляя старое и новое состояния документа.
Если вы работаете в управляемом интерфейсе, ручное управление пессимистическими блокировками через ЗаблокироватьДанныеДляРедактирования() часто избыточно. Платформа предоставляет более высокоуровневый и безопасный механизм блокирования данных формы. Рассмотрим использование методов ЗаблокироватьДанныеФормыДляРедактирования() и РазблокироватьДанныеФормыДляРедактирования().
Эти методы привязываются непосредственно к контексту управляемой формы и автоматически очищают все связанные блокировки при закрытии формы или уничтожении ее контекста на сервере, исключая человеческий фактор и ошибки «зависших» сеансов.
Пример интеграции в код управляемой формы:
&НаСервере
Процедура ЗаблокироватьФормуНаСервере()
// Блокируем данные формы, привязанной к объекту
Попытка
ЗаблокироватьДанныеФормыДляРедактирования();
Исключение
ВызватьИсключение "Не удалось заблокировать документ для редактирования. " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
&НаСервере
Процедура РазблокироватьФормуНаСервере()
РазблокироватьДанныеФормыДляРедактирования();
КонецПроцедуры
Подводя итоги, сформулируем основные правила работы с пессимистическими блокировками для разработчиков 1С:
ЗаблокироватьДанныеДляРедактирования() повторно без предварительного вызова РазблокироватьДанныеДляРедактирования() в рамках одной логической цепочки работы с формой.