В современных языках программирования (JavaScript, Python, C#) широко распространена практика передачи функций в качестве аргументов другим функциям. Это позволяет создавать гибкий код, реализовывать callback-механизмы и асинхронную обработку. В среде разработки 1С:Предприятие подход отличается: язык 1С не поддерживает функции как объекты первого класса (first-class citizens). Мы не можем напрямую присвоить функцию переменной или передать ее как объект.
Однако, потребность в таких механизмах возникает регулярно. В данной статье мы подробно разберем, как обойти это ограничение, используя штатные механизмы платформы, и решим задачу автора по параллельной обработке данных при загрузке из внешней системы.
Самый простой, но и самый "опасный" способ имитации передачи функции — это передача ее имени или кода в виде строки с последующим выполнением. В платформе для этого существуют операторы Выполнить() и функция Вычислить().
Разберем, как это работает. Допустим, мы хотим создать универсальную функцию, которая принимает имя алгоритма и данные для обработки.
Для процедур (без возврата значения):
Используем оператор Выполнить. Он компилирует и запускает переданный ему строковый фрагмент кода.
// Пример универсальной процедуры-обертки
Процедура ВызватьПроцедуруПоИмени(ИмяПроцедуры, Параметр) Экспорт
// Формируем строку кода, которую платформа должна выполнить
// Важно: переменная "Параметр" должна быть видна в контексте выполнения
ТекстКода = ИмяПроцедуры + "(Параметр);";
// Запускаем выполнение
Выполнить(ТекстКода);
КонецПроцедуры
// Пример использования
Процедура МойАлгоритм(Данные) Экспорт
Сообщить("Обрабатываем: " + Данные);
КонецПроцедуры
// Вызов
ВызватьПроцедуруПоИмени("МойАлгоритм", "Тестовые данные");
Для функций (с возвратом значения):
Если нам нужно получить результат, используем функцию Вычислить. Она вычисляет выражение, переданное в строке, и возвращает результат.
// Пример универсальной функции-обертки
Функция ВызватьФункциюПоИмени(ИмяФункции, Значение) Экспорт
ТекстВыражения = ИмяФункции + "(Значение)";
Возврат Вычислить(ТекстВыражения);
КонецФункции
// Пример использования
Функция УмножитьНаДва(Число) Экспорт
Возврат Число * 2;
КонецФункции
// Вызов
Результат = ВызватьФункциюПоИмени("УмножитьНаДва", 10); // Вернет 20
Ограничения и риски данного подхода:
Выполнить/Вычислить сложнее, так как контекст формируется динамически.Начиная с версии 8.3, платформа 1С активно использует асинхронность в интерфейсе. Для этого был введен специальный тип данных ОписаниеОповещения. Это и есть "официальный" способ передачи ссылки на процедуру.
Давайте посмотрим, как это применить для нашей задачи. Мы можем создать описание оповещения, которое содержит информацию о том, какую процедуру вызвать и в каком модуле она находится.
// Создаем описание оповещения.
// Первый параметр - имя процедуры-обработчика (строкой).
// Второй параметр - модуль или форма, где эта процедура находится (ЭтотОбъект).
// Третий параметр - дополнительные данные, которые будут переданы в обработчик.
Оповещение = Новый ОписаниеОповещения("ИмяПроцедурыОбработки", ЭтотОбъект, ДополнительныеПараметры);
// Передаем это оповещение в нашу универсальную функцию
ВыполнитьУниверсальноеДействие(Данные, Оповещение);
...
// Внутри универсальной функции
Процедура ВыполнитьУниверсальноеДействие(Данные, ОповещениеОЗавершении)
// Какая-то логика...
Результат = Данные * 2;
// Вызываем переданную процедуру
ВыполнитьОбработкуОповещения(ОповещениеОЗавершении, Результат);
КонецПроцедуры
Этот метод намного безопаснее и "чище" с точки зрения архитектуры 1С, чем использование Вычислить.
Вместо передачи имени функции, мы можем передать объект (например, Обработку), у которого есть заранее известный метод (интерфейс). Это классический подход ООП.
Предположим, у нас есть разные алгоритмы обработки данных. Мы создаем под каждый алгоритм отдельную Обработку.
// Функция принимает не имя метода, а объект-обработчик
Процедура ОбработатьСписок(СписокДанных, ОбъектСтратегии) Экспорт
Для Каждого Элемент Из СписокДанных Цикл
// Мы точно знаем, что у переданного объекта есть метод "ВыполнитьАлгоритм"
ОбъектСтратегии.ВыполнитьАлгоритм(Элемент);
КонецЦикла;
КонецПроцедуры
При вызове мы создаем нужную обработку:
Обработчик = Обработки.ЗагрузкаТоваров.Создать();
ОбработатьСписок(МойСписок, Обработчик);
Этот способ идеален для построения сложной архитектуры, так как позволяет инкапсулировать (скрыть) логику и состояние внутри объекта обработки.
Автор вопроса столкнулся с конкретной проблемой: внешняя система отдает данные порциями (страницами) и требует ожидания между запросами. Чтобы не терять время, автор хочет обрабатывать полученные данные во время ожидания следующей порции.
В однопоточном синхронном коде (стандартное поведение сеанса 1С) это сделать сложно: пока работает HTTPСоединение.Получить() или работает НайтиПомеченныеНаУдаление(), поток занят и "ждет".
Для реализации параллелизма в 1С используется механизм Фоновых заданий. Рассмотрим архитектурно правильное решение этой задачи.
Алгоритм действий:
ФоновоеЗадание для их обработки.Пример реализации через фоновые задания:
Процедура ЗагрузитьДанныеИзВнешнейСистемы() Экспорт
Продолжать = Истина;
ПараметрыЗапроса = Новый Структура("Страница", 1);
Пока Продолжать Цикл
// 1. Получаем порцию данных (синхронно ждем ответ от API)
ОтветAPI = ПолучитьСтраницуСписка(ПараметрыЗапроса);
Если ОтветAPI.Данные.Количество() > 0 Тогда
// 2. Готовим параметры для фоновой обработки
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(ОтветAPI.Данные);
// 3. ЗАПУСКАЕМ ОБРАБОТКУ В ФОНЕ
// Основной поток освобождается мгновенно после запуска задания
ФоновыеЗадания.Выполнить("МодульОбработкиДанных.ОбработатьПорцию",
МассивПараметров,
Новый УникальныйИдентификатор,
"Обработка страницы " + ПараметрыЗапроса.Страница);
КонецЕсли;
// Анализируем, есть ли следующая страница
Продолжать = ОтветAPI.ЕстьЕще;
ПараметрыЗапроса.Страница = ПараметрыЗапроса.Страница + 1;
// Если API требует паузы, делаем её здесь.
// В это время фоновые задания уже вовсю обрабатывают предыдущие порции.
Если ОтветAPI.НеобходимоПодождать > 0 Тогда
// Пауза в секундах. Обратите внимание: это блокирует основной поток,
// но НЕ блокирует запущенные фоновые задания.
Задержка(ОтветAPI.НеобходимоПодождать);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Метод, который будет выполняться в фоне
Процедура ОбработатьПорцию(Данные) Экспорт
// Сложная и долгая логика записи в базу, проведения документов и т.д.
// ...
КонецПроцедуры
При использовании подхода с фоновыми заданиями следует учитывать несколько нюансов:
rphost. Рекомендуется использовать пул фоновых заданий или очередь (например, через Регистр Сведений), чтобы ограничивать количество одновременных потоков.ФоновыеЗадания.Выполнить, должны быть сериализуемыми (простые типы, структуры, массивы, но не сложные объекты вроде ДокументОбъект).Хотя 1С не позволяет передавать функции так же свободно, как JavaScript, мы рассмотрели эффективные способы решения этой задачи:
ОписаниеОповещения для организации чистого кода с обратными вызовами.Вычислить/Выполнить с осторожностью и только для простейших случаев.ФоновыеЗадания, разделяя процесс получения данных и их обработки.