Как организовать очередь асинхронных вызовов и обеспечить откат транзакций при работе с несколькими терминалами

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

В современной разработке на платформе 1С:Предприятие 8.3, особенно в решениях для розничной торговли вроде 1С:Касса или 1С:Розница, разработчики часто сталкиваются с необходимостью интеграции с внешним оборудованием. Основная сложность заключается в том, что большинство методов работы с драйверами торгового оборудования являются асинхронными. Если перед нами стоит задача провести оплату одного чека сразу через несколько платежных терминалов (например, разделение суммы), стандартный цикл Для каждого не сработает корректно. Асинхронные вызовы «выстрелят» одновременно, создав хаос в банковских операциях, а отсутствие контроля за результатом сделает невозможным откат успешных платежей в случае ошибки на одном из этапов.

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

Проблема асинхронности и «гонки» запросов

Рассмотрим стандартную ситуацию: у нас есть таблица значений с перечнем терминалов и суммами. В управляемом приложении вызов метода печати чека или оплаты по карте требует использования ОписаниеОповещения. Если мы запустим обычный цикл, платформа инициирует все вызовы практически мгновенно, не дожидаясь ответа от оборудования. В результате:

  1. Банковские терминалы могут блокировать друг друга.
  2. Порядок завершения операций будет непредсказуемым.
  3. В случае ошибки на втором терминале, первый уже может успешно списать деньги, и система «забудет» об этом.

Проанализируем наиболее эффективные способы решения этой задачи.

Решение 1: Рекурсивный обход очереди через оповещения

Это классический подход для клиентской части 1С, позволяющий имитировать синхронную работу в асинхронной среде. Вместо цикла мы используем процедуру, которая вызывает сама себя по цепочке.

Разберем этот метод по шагам:

  1. Подготавливаем массив или таблицу данных для обработки.
  2. Создаем процедуру-обработчик, которая принимает индекс текущего элемента.
  3. После завершения операции в процедуре оповещения вызываем этот же обработчик, увеличивая индекс на единицу.

Рассмотрим пример кода:


&НаКлиенте
Процедура НачатьОбработкуОчереди()
    ДанныеОчереди = ПолучитьСписокТерминалов(); // ТЗ или Массив
    ОбработатьСледующийЭлемент(0, ДанныеОчереди);
КонецПроцедуры

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

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

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

Решение 2: Использование фоновых заданий (Серверный координатор)

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

Выясним причину, почему это надежнее: серверное ФоновоеЗадание не зависит от состояния окна браузера или тонкого клиента. Оно может последовательно опрашивать статусы терминалов, записывать промежуточные результаты в базу данных и гарантировать, что процесс не прервется на середине.

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

  1. Блокировка объектов оборудования через УправляемыеБлокировки, чтобы другие сеансы не вклинились в процесс.
  2. Последовательный вызов серверных методов API оборудования.
  3. Запись лога каждой операции в РегистрСведений.

Решение 3: Реализация паттерна «Сага» (Компенсирующие транзакции)

Поскольку платежные терминалы — это внешние системы, мы не можем использовать стандартное НачатьТранзакцию() и ОтменитьТранзакцию() для отката банковских операций. Нам необходимо реализовать программную компенсацию.

Проанализируем ситуацию: если на третьем терминале произошла ошибка, нам нужно вернуть деньги, списанные на первом и втором терминалах. Для этого мы должны использовать Стек (Stack) выполненных операций.

Алгоритм реализации:

  1. При успешном выполнении каждой операции мы помещаем данные транзакции (Идентификатор, Терминал, Сумма) в массив УспешныеДействия.
  2. Если возникает ошибка, мы запускаем цикл по массиву УспешныеДействия с конца (от последнего к первому).
  3. Для каждого элемента вызываем метод ОтменитьОперацию или ВозвратЧека.

Пример логики отката:


&НаКлиенте
Процедура ЗапуститьОткатОпераций(ТекущийИндекс, ДанныеОчереди)
    
    // Мы идем в обратном порядке от текущего неудачного индекса
    Для Счетчик = 1 По ТекущийИндекс Цикл
        ИндексДляОтката = ТекущийИндекс - Счетчик;
        ДанныеДляОтката = ДанныеОчереди[ИндексДляОтката];
        
        // Вызов метода возврата на терминале
        МенеджерОборудованияКлиент.ВыполнитьВозврат(ДанныеДляОтката.ИдентификаторТранзакции);
    КонецЦикла;
    
    Сообщить("Произошла ошибка. Все частичные оплаты были отменены.");
КонецПроцедуры

Решение 4: Очередь через Регистр Сведений (Семафоры)

Если операций много и они приходят из разных источников, стоит рассмотреть вариант с использованием Регистра Сведений как очереди заданий. Это особенно актуально для облачных решений.

Разберем по шагам:

  1. Создаем независимый непериодический регистр ОчередьЧеков.
  2. При необходимости пробития записываем туда строки со статусом Новый.
  3. Регламентное задание на сервере или обработчик ожидания на клиенте выбирает одну запись со статусом Новый, меняет статус на ВРаботе и начинает выполнение.
  4. По завершении статус меняется на Выполнено или Ошибка.

Использование регистра позволяет избежать потери данных при перезагрузке компьютера или обрыве связи. Как отмечалось в обсуждении, запись в регистр в облаке может занимать время, но это мизерные затраты по сравнению с временем ожидания ответа от банковского сервера (которое может длиться до 30-60 секунд).

Решение 5: Использование возможностей БПО (Библиотека подключаемого оборудования)

Не забываем, что в современных конфигурациях на базе БСП (Библиотека стандартных подсистем) уже существуют механизмы для работы с очередями чеков. В БПО есть справочник ОчередьЧеков, предназначенный именно для таких целей. Перед написанием своего велосипеда, рекомендуем проверить, не реализована ли уже поддержка пакетного пробития в вашей версии 1С:Касса. Часто достаточно просто добавить записи в этот справочник, а штатные механизмы БПО сами разберутся с очередностью и повторными попытками в случае сбоев.

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

← На главную