Как передать функцию в качестве параметра в другую функцию и реализовать механизм обратного вызова (Callback) в 1С?

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

В современных языках программирования (JavaScript, Python, C#) широко распространена практика передачи функций в качестве аргументов другим функциям. Это позволяет создавать гибкий код, реализовывать callback-механизмы и асинхронную обработку. В среде разработки 1С:Предприятие подход отличается: язык 1С не поддерживает функции как объекты первого класса (first-class citizens). Мы не можем напрямую присвоить функцию переменной или передать ее как объект.

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

Вариант 1. Использование метапрограммирования: Выполнить и Вычислить

Самый простой, но и самый "опасный" способ имитации передачи функции — это передача ее имени или кода в виде строки с последующим выполнением. В платформе для этого существуют операторы Выполнить() и функция Вычислить().

Разберем, как это работает. Допустим, мы хотим создать универсальную функцию, которая принимает имя алгоритма и данные для обработки.

Для процедур (без возврата значения):

Используем оператор Выполнить. Он компилирует и запускает переданный ему строковый фрагмент кода.


// Пример универсальной процедуры-обертки
Процедура ВызватьПроцедуруПоИмени(ИмяПроцедуры, Параметр) Экспорт
    
    // Формируем строку кода, которую платформа должна выполнить
    // Важно: переменная "Параметр" должна быть видна в контексте выполнения
    ТекстКода = ИмяПроцедуры + "(Параметр);";
    
    // Запускаем выполнение
    Выполнить(ТекстКода);
    
КонецПроцедуры

// Пример использования
Процедура МойАлгоритм(Данные) Экспорт
    Сообщить("Обрабатываем: " + Данные);
КонецПроцедуры

// Вызов
ВызватьПроцедуруПоИмени("МойАлгоритм", "Тестовые данные");

Для функций (с возвратом значения):

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


// Пример универсальной функции-обертки
Функция ВызватьФункциюПоИмени(ИмяФункции, Значение) Экспорт
    
    ТекстВыражения = ИмяФункции + "(Значение)";
    Возврат Вычислить(ТекстВыражения);
    
КонецФункции

// Пример использования
Функция УмножитьНаДва(Число) Экспорт
    Возврат Число * 2;
КонецФункции

// Вызов
Результат = ВызватьФункциюПоИмени("УмножитьНаДва", 10); // Вернет 20

Ограничения и риски данного подхода:

  1. Безопасность: Если имя функции формируется из внешних данных, злоумышленник может внедрить произвольный код (Code Injection). Всегда проверяйте входные данные.
  2. Отладка: Отлаживать код внутри Выполнить/Вычислить сложнее, так как контекст формируется динамически.
  3. Контекст: Процедуры, вызываемые таким образом, должны быть доступны в текущем контексте (обычно это Экспортные методы модуля или методы глобального контекста).

Вариант 2. Штатный механизм: ОписаниеОповещения

Начиная с версии 8.3, платформа 1С активно использует асинхронность в интерфейсе. Для этого был введен специальный тип данных ОписаниеОповещения. Это и есть "официальный" способ передачи ссылки на процедуру.

Давайте посмотрим, как это применить для нашей задачи. Мы можем создать описание оповещения, которое содержит информацию о том, какую процедуру вызвать и в каком модуле она находится.


// Создаем описание оповещения. 
// Первый параметр - имя процедуры-обработчика (строкой).
// Второй параметр - модуль или форма, где эта процедура находится (ЭтотОбъект).
// Третий параметр - дополнительные данные, которые будут переданы в обработчик.
Оповещение = Новый ОписаниеОповещения("ИмяПроцедурыОбработки", ЭтотОбъект, ДополнительныеПараметры);

// Передаем это оповещение в нашу универсальную функцию
ВыполнитьУниверсальноеДействие(Данные, Оповещение);

...

// Внутри универсальной функции
Процедура ВыполнитьУниверсальноеДействие(Данные, ОповещениеОЗавершении)
    
    // Какая-то логика...
    Результат = Данные * 2;
    
    // Вызываем переданную процедуру
    ВыполнитьОбработкуОповещения(ОповещениеОЗавершении, Результат);
    
КонецПроцедуры

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

Вариант 3. Объектный подход (Паттерн "Стратегия")

Вместо передачи имени функции, мы можем передать объект (например, Обработку), у которого есть заранее известный метод (интерфейс). Это классический подход ООП.

Предположим, у нас есть разные алгоритмы обработки данных. Мы создаем под каждый алгоритм отдельную Обработку.


// Функция принимает не имя метода, а объект-обработчик
Процедура ОбработатьСписок(СписокДанных, ОбъектСтратегии) Экспорт
    
    Для Каждого Элемент Из СписокДанных Цикл
        // Мы точно знаем, что у переданного объекта есть метод "ВыполнитьАлгоритм"
        ОбъектСтратегии.ВыполнитьАлгоритм(Элемент);
    КонецЦикла;

КонецПроцедуры

При вызове мы создаем нужную обработку:


Обработчик = Обработки.ЗагрузкаТоваров.Создать();
ОбработатьСписок(МойСписок, Обработчик);

Этот способ идеален для построения сложной архитектуры, так как позволяет инкапсулировать (скрыть) логику и состояние внутри объекта обработки.

Решение задачи с параллельной загрузкой и API

Автор вопроса столкнулся с конкретной проблемой: внешняя система отдает данные порциями (страницами) и требует ожидания между запросами. Чтобы не терять время, автор хочет обрабатывать полученные данные во время ожидания следующей порции.

В однопоточном синхронном коде (стандартное поведение сеанса 1С) это сделать сложно: пока работает HTTPСоединение.Получить() или работает НайтиПомеченныеНаУдаление(), поток занят и "ждет".

Для реализации параллелизма в 1С используется механизм Фоновых заданий. Рассмотрим архитектурно правильное решение этой задачи.

Алгоритм действий:

  1. Основной поток запрашивает первую страницу данных у внешнего API.
  2. Как только данные получены, основной поток запускает ФоновоеЗадание для их обработки.
  3. Основной поток, не дожидаясь завершения обработки, переходит к ожиданию (если API требует паузы) или сразу запрашивает следующую страницу.
  4. Цикл повторяется.

Пример реализации через фоновые задания:


Процедура ЗагрузитьДанныеИзВнешнейСистемы() Экспорт
    
    Продолжать = Истина;
    ПараметрыЗапроса = Новый Структура("Страница", 1);
    
    Пока Продолжать Цикл
        
        // 1. Получаем порцию данных (синхронно ждем ответ от API)
        ОтветAPI = ПолучитьСтраницуСписка(ПараметрыЗапроса);
        
        Если ОтветAPI.Данные.Количество() > 0 Тогда
            
            // 2. Готовим параметры для фоновой обработки
            МассивПараметров = Новый Массив;
            МассивПараметров.Добавить(ОтветAPI.Данные);
            
            // 3. ЗАПУСКАЕМ ОБРАБОТКУ В ФОНЕ
            // Основной поток освобождается мгновенно после запуска задания
            ФоновыеЗадания.Выполнить("МодульОбработкиДанных.ОбработатьПорцию", 
                                      МассивПараметров, 
                                      Новый УникальныйИдентификатор, 
                                      "Обработка страницы " + ПараметрыЗапроса.Страница);
                                      
        КонецЕсли;
        
        // Анализируем, есть ли следующая страница
        Продолжать = ОтветAPI.ЕстьЕще;
        ПараметрыЗапроса.Страница = ПараметрыЗапроса.Страница + 1;
        
        // Если API требует паузы, делаем её здесь.
        // В это время фоновые задания уже вовсю обрабатывают предыдущие порции.
        Если ОтветAPI.НеобходимоПодождать > 0 Тогда
            // Пауза в секундах. Обратите внимание: это блокирует основной поток, 
            // но НЕ блокирует запущенные фоновые задания.
            Задержка(ОтветAPI.НеобходимоПодождать); 
        КонецЕсли;
        
    КонецЦикла;
    
КонецПроцедуры

// Метод, который будет выполняться в фоне
Процедура ОбработатьПорцию(Данные) Экспорт
    // Сложная и долгая логика записи в базу, проведения документов и т.д.
    // ...
КонецПроцедуры

Важные замечания по использованию фоновых заданий

При использовании подхода с фоновыми заданиями следует учитывать несколько нюансов:

  1. Лицензирование и ресурсы: Каждое фоновое задание — это отдельный сеанс на сервере 1С. Если вы запустите 100 заданий параллельно, вы можете исчерпать лицензии или перегрузить рабочий процесс rphost. Рекомендуется использовать пул фоновых заданий или очередь (например, через Регистр Сведений), чтобы ограничивать количество одновременных потоков.
  2. Передача параметров: Параметры, передаваемые в метод ФоновыеЗадания.Выполнить, должны быть сериализуемыми (простые типы, структуры, массивы, но не сложные объекты вроде ДокументОбъект).
  3. Файловая база: В файловом варианте работы базы данных фоновые задания эмулируются и выполняются последовательно (если не запущен отдельный сеанс с обработчиком ожиданий), поэтому реального ускорения можно не получить. Это решение полноценно работает в клиент-серверном варианте.

Резюме

Хотя 1С не позволяет передавать функции так же свободно, как JavaScript, мы рассмотрели эффективные способы решения этой задачи:

← На главную