A1sCode библиотека 1С

Идея дня

Если в вашей сборке A1sLocks уже есть готовые процедуры (например, TryAcquire/Release), используйте их. Ниже — эталонные приёмы, которые можно положить в свой модуль.

Логическая блокировка по имени (через регистр сведений)

Создайте простой регистр сведений БлокировкиДанных (измерения: Ключ строка; ресурсы: Владелец строка, Истекает дата). На Ключ поставьте уникальный индекс.


Функция _СеансID() Экспорт
    Возврат A1sS.AsString(ТекущийПользователь()); // при желании добавьте хост/UUID
КонецФункции

Функция TryAcquireNamedLock(Ключ, TTLСек = 60) Экспорт
    Если A1sO.Empty(Ключ) Тогда Возврат Ложь; КонецЕсли;
    НачатьТранзакцию();
    Попытка
        // 1) Снимем просроченные блоки по ключу
        Запрос = Новый Запрос("
        |ВЫБРАТЬ
        |    Бл.Ссылка
        |ИЗ РегистрСведений.БлокировкиДанных КАК Бл
        |ГДЕ Бл.Ключ = &Ключ И Бл.Истекает < &Сейчас
        ");
        Таб = A1sQ.Unload(Запрос.ТекстЗапроса, Ключ, ТекущаяДата()); // позиционные параметры

        Для каждого Стр Из Таб Цикл
            Набор = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
            Набор.Отбор.Ссылка.Установить(Стр.Ссылка);
            Набор.Прочитать();
            Набор.Записи.Очистить(); // удалим запись
            Набор.Записать();
        КонецЦикла;

        // 2) Попробуем создать новую запись-блокировку
        Набор2 = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
        Зап = Набор2.Записи.Добавить();
        Зап.Ключ     = A1sS.AsString(Ключ);
        Зап.Владелец = _СеансID();
        Зап.Истекает = ДобавитьСекунды(ТекущаяДата(), TTLСек);
        Набор2.Записать(); // упадёт на уникальном индексе, если уже занято

        ЗафиксироватьТранзакцию();
        Возврат Истина;
    Исключение
        ОтменитьТранзакцию();
        Возврат Ложь; // не удалось захватить
    КонецПопытки;
КонецФункции

Процедура ReleaseNamedLock(Ключ) Экспорт
    Если A1sO.Empty(Ключ) Тогда Возврат; КонецЕсли;
    НачатьТранзакцию();
    Попытка
        Запрос = Новый Запрос("
        |ВЫБРАТЬ
        |    Бл.Ссылка
        |ИЗ РегистрСведений.БлокировкиДанных КАК Бл
        |ГДЕ Бл.Ключ = &Ключ
        ");
        Таб = A1sQ.Unload(Запрос.ТекстЗапроса, Ключ);
        Для каждого Стр Из Таб Цикл
            Набор = РегистрСведений.БлокировкиДанных.СоздатьНаборЗаписей();
            Набор.Отбор.Ссылка.Установить(Стр.Ссылка);
            Набор.Прочитать();
            Набор.Записи.Очистить();
            Набор.Записать();
        КонецЦикла;
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        // Логируем, но не роняем общий поток
        A1sLog.Warn("Day28", "ReleaseNamedLock: ошибка освобождения", Новый Структура("Ключ", Ключ));
    КонецПопытки;
КонецПроцедуры
      

TTL защищает от «вечных» блоков (например, если сессия умерла). При каждом длинном действии можно «продлевать» блок, обновляя Истекает.

Шаблон использования с ретраями


Функция WithNamedLockDo(Ключ, ПроцедурныйКод) Экспорт
    // В 1С нельзя передать процедуру как значение ⇒ используйте этот шаблон вручную:
    Возврат Неопределено;
КонецФункции

Процедура ИмпортКаталога() Экспорт
    ЛокКлюч = "import:catalog";

    МаксПопыток = 10; ПаузаСек = 0.3;
    Для П = 1 По МаксПопыток Цикл
        Если TryAcquireNamedLock(ЛокКлюч, 120) Тогда
            Попытка
                A1sLog.Info("Day28", "Импорт стартовал (lock ok)");
                // ... критическая секция: читаем файлы, пишем объекты ...
            Наконец
                ReleaseNamedLock(ЛокКлюч);
                A1sLog.Info("Day28", "Импорт завершён (unlock)");
            КонецПопытки;
            Возврат;
        Иначе
            Пауза(ПаузаСек);
        КонецЕсли;
    КонецЦикла;

    A1sLog.Warn("Day28", "Импорт пропущен: не удалось взять блокировку");
КонецПроцедуры
      

Пессимистический подход: запись с ретраями

Когда блокировку явно взять нельзя/не нужно — делаем «мягкий» ретрай при записи.


Функция SafeWrite(Объект, МаксПопыток = 5, ПаузаСек = 0.2) Экспорт
    Для П = 1 По МаксПопыток Цикл
        Попытка
            НачатьТранзакцию();
            Объект.Записать();
            ЗафиксироватьТранзакцию();
            Возврат Истина;
        Исключение
            ОтменитьТранзакцию();
            Пауза(ПаузаСек);
        КонецПопытки;
    КонецЦикла;
    Возврат Ложь;
КонецФункции

// Использование
Процедура ОбновитьЦену(Ссылка, НоваяЦена) Экспорт
    Если A1sO.Empty(Ссылка) Тогда Возврат; КонецЕсли;
    Об = Ссылка.ПолучитьОбъект();
    Об.Цена = A1sO.RoundNum(A1sO.NumOrZero(НоваяЦена), 2);
    Если НЕ SafeWrite(Об) Тогда
        A1sLog.Error("Day28", "Не удалось записать", Новый Структура("Ссылка,Цена", Ссылка, НоваяЦена));
    КонецЕсли;
КонецПроцедуры
      

Чек-лист безопасной работы

Практика (15–30 минут)

  1. Создайте регистр сведений БлокировкиДанных и включите уникальный индекс по полю Ключ.
  2. Реализуйте TryAcquireNamedLock, ReleaseNamedLock и примените в одном критическом сценарии.
  3. Добавьте ретрай при записи объекта через SafeWrite и проверьте поведение при конкуренции.
  4. В отчёте/логе выведите статистику: сколько раз блок брался/не брался, среднее время ожидания.

Итоги курса

Поздравляем! За 28 дней вы собрали рабочий «набор инструментов» A1sCode: быстрые запросы (A1sQ), надёжные утилиты объектов/строк (A1sO/A1sS), логирование (A1sLog), сериализацию (A1sJ/A1sX) и практики работы с массивами (A1sArrays). Сегодня вы дополнили это безопасной конкурирующей записью (A1sLocks). Удачи в проектах!

Вернуться к оглавлению курса →

Примечание

Код и тексты на этой странице сгенерированы ИИ. Возможны неточности и ошибки. Перед использованием проверяйте и адаптируйте примеры под вашу версию платформы 1С и сборку библиотеки A1sCode.