Как дождаться завершения всех асинхронных процедур перед выполнением следующего шага в 1С

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

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

Анализ проблемы: почему стандартный цикл не работает

Рассмотрим ситуацию, с которой столкнулся автор темы. В коде используется цикл Для каждого, внутри которого вызывается метод НачатьПомещениеФайлаНаСервер. Этот метод является асинхронным: он инициирует процесс передачи файла и сразу передает управление следующей строке кода. Программа не ждет, пока файл физически окажется на сервере и отработает процедура ОбработатьФайлНаСервереЗавершение.

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

Способ 1. Паттерн «Счетчик завершений» (для старых версий платформы)

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

Проанализируем шаги реализации:

  1. Перед началом цикла определим общее количество файлов, которые нужно обработать.
  2. Передадим это количество и текущий счетчик через структуру ДополнительныеПараметры в объект ОписаниеОповещения.
  3. В процедуре завершения будем уменьшать счетчик.
  4. Когда счетчик достигнет нуля, вызовем финальный метод ПрочитатьДанные().

Посмотрим на пример реализации кода в модуле формы:


&НаКлиенте
Процедура ЗагрузитьФайлыИОбновить()
    
    ВсегоФайлов = ТаблицаДанных.Количество();
    Если ВсегоФайлов = 0 Тогда
        Возврат;
    КонецЕсли;

    // Создаем структуру для хранения состояния процесса
    КонтекстЗагрузки = Новый Структура;
    КонтекстЗагрузки.Вставить("ОсталосьОбработать", ВсегоФайлов);

    Для каждого СтрокаДанных Из ТаблицаДанных Цикл
        
        ДопПараметры = Новый Структура("Контекст", КонтекстЗагрузки);
        Оповещение = Новый ОписаниеОповещения("ОбработатьФайлЗавершение", ЭтотОбъект, ДопПараметры);
        
        НачатьПомещениеФайлаНаСервер(Оповещение, , , , СтрокаДанных.ПутьКФайлу, УникальныйИдентификатор);
        
    КонецЦикла;

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

&НаКлиенте
Процедура ОбработатьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДопПараметры) Экспорт
    
    // Выполняем серверную обработку одного файла
    ОбработатьОдинФайлНаСервере(Адрес);
    
    // Работаем со счетчиком
    Контекст = ДопПараметры.Контекст;
    Контекст.ОсталосьОбработать = Контекст.ОсталосьОбработать - 1;
    
    // Проверяем, все ли файлы обработаны
    Если Контекст.ОсталосьОбработать = 0 Тогда
        ПрочитатьДанные();
    КонецЕсли;
    
КонецПроцедуры

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

Способ 2. Использование множественной загрузки (Платформа 8.3.15+)

Если версия вашей платформы выше 8.3.15, мы рекомендуем отказаться от цикла с одиночными вызовами в пользу метода НачатьПомещениеФайловНаСервер (во множественном числе). Этот метод специально разработан для работы с массивом файлов.

Рассмотрим преимущества этого подхода:

Пример реализации будет выглядеть следующим образом:


&НаКлиенте
Процедура ЗагрузитьВсеФайлыСразу()
    
    МассивФайлов = Новый Массив;
    Для каждого Строка Из ТаблицаДанных Цикл
        ОписаниеФайла = Новый ОписаниеПередаваемогоФайла(Строка.ПутьКФайлу);
        МассивФайлов.Добавить(ОписаниеФайла);
    КонецЦикла;
    
    Оповещение = Новый ОписаниеОповещения("ПослеЗагрузкиВсейПачки", ЭтотОбъект);
    НачатьПомещениеФайловНаСервер(Оповещение, МассивФайлов, , Ложь, УникальныйИдентификатор);
    
КонецПроцедуры

&НаКлиенте
Процедура ПослеЗагрузкиВсейПачки(Результат, ДопПараметры) Экспорт
    // Сюда мы попадем только когда ВСЕ файлы из массива будут на сервере
    ПрочитатьДанные();
КонецПроцедуры

Способ 3. Современный подход через Async/Await (Платформа 8.3.18+)

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

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


&НаКлиенте
Асинх Процедура ЗагрузитьФайлыАсинхронно()
    
    Для каждого СтрокаДанных Из ТаблицаДанных Цикл
        
        // Используем асинхронный аналог метода
        Результат = Ждать ПоместитьФайлНаСерверАсинх(, , , СтрокаДанных.ПутьКФайлу, УникальныйИдентификатор);
        
        // Код ниже Ждать не выполнится, пока файл не загрузится
        Если Результат <> Неопределено Тогда
            ОбработатьФайлНаСервере(Результат.Адрес);
        КонецЕсли;
        
    КонецЦикла;
    
    // Эта строка выполнится строго ПОСЛЕ завершения цикла и всех загрузок
    ПрочитатьДанные();
    
КонецПроцедуры

Обратите внимание: если файлов очень много, последовательное ожидание каждого файла через Ждать внутри цикла может быть медленнее, чем параллельная загрузка. Однако с точки зрения читаемости кода и логики «выполнить после» — это идеальное решение.

Особенности обработки ошибок в асинхронных цепочках

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

Разберем, как обезопасить код:

Почему «ВыполнитьОбработкуОповещения» не решало задачу?

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

Чтобы "протолкнуть" выполнение метода в самый конец очереди, иногда применяют обработчик ожидания:

ПодключитьОбработчикОжидания("ПрочитатьДанные", 0.1, Истина);

Это заставляет платформу дождаться, пока стек вызовов полностью очистится (включая все завершившиеся асинхронные загрузки), и только потом выполнить чтение данных. Однако этот метод считается менее надежным, чем явное использование счетчиков или Async/Await.

Резюме

Для надежного выполнения кода после асинхронных операций в 1С выберите подходящий метод в зависимости от вашей платформы:

  1. Для новых версий (8.3.18+): используйте Асинх и Ждать. Это самый современный и понятный стандарт.
  2. Для массовых операций (8.3.15+): используйте методы множественной загрузки (НачатьПомещениеФайловНаСервер).
  3. Для поддержки старых конфигураций: реализуйте структуру-контекст со счетчиком ОсталосьОбработать, передавая её через ДополнительныеПараметры в ОписаниеОповещения.

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

← На главную