При разработке на платформе 1С:Предприятие 8.3, особенно в режиме управляемого приложения, разработчики часто сталкиваются с асинхронностью. Типичная ситуация: нам нужно загрузить несколько файлов на сервер и только после того, как все файлы будут обработаны, обновить данные в форме. Однако из-за особенностей событийной модели 1С код, написанный сразу после вызова асинхронной функции, выполняется мгновенно, не дожидаясь завершения фоновых процессов. Разберем подробно, как управлять этой логикой и какие инструменты предоставляет платформа для решения этой задачи. Для понимания базовых принципов можно изучить руководство по работе с файлами в управляемых формах 1С 8.3.
Рассмотрим ситуацию, с которой столкнулся автор темы. В коде используется цикл Для каждого, внутри которого вызывается метод НачатьПомещениеФайлаНаСервер. Этот метод является асинхронным: он инициирует процесс передачи файла и сразу передает управление следующей строке кода. Программа не ждет, пока файл физически окажется на сервере и отработает процедура ОбработатьФайлНаСервереЗавершение.
В результате процедура ПрочитатьДанные(), стоящая за циклом, вызывается еще до того, как первый файл успел загрузиться. Попытка вызвать её через ВыполнитьОбработкуОповещения также не приносит успеха, так как этот метод выполняет код синхронно, просто добавляя его в текущий поток выполнения. Выясним, как заставить платформу соблюдать нужную нам последовательность.
Этот метод является классическим и наиболее универсальным, так как работает на любой версии платформы 8.3, поддерживающей ОписаниеОповещения. Суть метода заключается в том, что мы заранее знаем количество итераций и в каждом обратном вызове проверяем, не является ли текущая итерация последней.
Проанализируем шаги реализации:
ДополнительныеПараметры в объект ОписаниеОповещения.ПрочитатьДанные().Посмотрим на пример реализации кода в модуле формы:
&НаКлиенте
Процедура ЗагрузитьФайлыИОбновить()
ВсегоФайлов = ТаблицаДанных.Количество();
Если ВсегоФайлов = 0 Тогда
Возврат;
КонецЕсли;
// Создаем структуру для хранения состояния процесса
КонтекстЗагрузки = Новый Структура;
КонтекстЗагрузки.Вставить("ОсталосьОбработать", ВсегоФайлов);
Для каждого СтрокаДанных Из ТаблицаДанных Цикл
ДопПараметры = Новый Структура("Контекст", КонтекстЗагрузки);
Оповещение = Новый ОписаниеОповещения("ОбработатьФайлЗавершение", ЭтотОбъект, ДопПараметры);
НачатьПомещениеФайлаНаСервер(Оповещение, , , , СтрокаДанных.ПутьКФайлу, УникальныйИдентификатор);
КонецЦикла;
КонецПроцедуры
&НаКлиенте
Процедура ОбработатьФайлЗавершение(Результат, Адрес, ВыбранноеИмяФайла, ДопПараметры) Экспорт
// Выполняем серверную обработку одного файла
ОбработатьОдинФайлНаСервере(Адрес);
// Работаем со счетчиком
Контекст = ДопПараметры.Контекст;
Контекст.ОсталосьОбработать = Контекст.ОсталосьОбработать - 1;
// Проверяем, все ли файлы обработаны
Если Контекст.ОсталосьОбработать = 0 Тогда
ПрочитатьДанные();
КонецЕсли;
КонецПроцедуры
Важный нюанс: использование структуры позволяет передавать данные по ссылке между вызовами оповещений, что делает управление состоянием очень удобным.
Если версия вашей платформы выше 8.3.15, мы рекомендуем отказаться от цикла с одиночными вызовами в пользу метода НачатьПомещениеФайловНаСервер (во множественном числе). Этот метод специально разработан для работы с массивом файлов.
Рассмотрим преимущества этого подхода:
Пример реализации будет выглядеть следующим образом:
&НаКлиенте
Процедура ЗагрузитьВсеФайлыСразу()
МассивФайлов = Новый Массив;
Для каждого Строка Из ТаблицаДанных Цикл
ОписаниеФайла = Новый ОписаниеПередаваемогоФайла(Строка.ПутьКФайлу);
МассивФайлов.Добавить(ОписаниеФайла);
КонецЦикла;
Оповещение = Новый ОписаниеОповещения("ПослеЗагрузкиВсейПачки", ЭтотОбъект);
НачатьПомещениеФайловНаСервер(Оповещение, МассивФайлов, , Ложь, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеЗагрузкиВсейПачки(Результат, ДопПараметры) Экспорт
// Сюда мы попадем только когда ВСЕ файлы из массива будут на сервере
ПрочитатьДанные();
КонецПроцедуры
В современных конфигурациях (начиная с 8.3.18) 1С внедрила механизм асинхронных функций, который позволяет писать асинхронный код так, будто он синхронный. Это самый элегантный способ, упомянутый в сообщениях форума.
Проанализируем, как работает оператор Ждать. Он приостанавливает выполнение текущей процедуры до момента завершения асинхронной операции, при этом интерфейс программы не "зависает".
&НаКлиенте
Асинх Процедура ЗагрузитьФайлыАсинхронно()
Для каждого СтрокаДанных Из ТаблицаДанных Цикл
// Используем асинхронный аналог метода
Результат = Ждать ПоместитьФайлНаСерверАсинх(, , , СтрокаДанных.ПутьКФайлу, УникальныйИдентификатор);
// Код ниже Ждать не выполнится, пока файл не загрузится
Если Результат <> Неопределено Тогда
ОбработатьФайлНаСервере(Результат.Адрес);
КонецЕсли;
КонецЦикла;
// Эта строка выполнится строго ПОСЛЕ завершения цикла и всех загрузок
ПрочитатьДанные();
КонецПроцедуры
Обратите внимание: если файлов очень много, последовательное ожидание каждого файла через Ждать внутри цикла может быть медленнее, чем параллельная загрузка. Однако с точки зрения читаемости кода и логики «выполнить после» — это идеальное решение.
При использовании любого из вышеперечисленных методов крайне важно обрабатывать исключительные ситуации. Если в процессе загрузки одного из десяти файлов произойдет ошибка, выполнение ПрочитатьДанные() может либо не произойти вовсе (в случае со счетчиком), либо произойти с некорректными данными.
Разберем, как обезопасить код:
Попытка...Исключение должен находиться внутри процедуры завершения. Счетчик должен уменьшаться в любом случае (даже при ошибке), чтобы не заблокировать выполнение финального метода.Попытка, чтобы гарантировать вызов ПрочитатьДанные() даже если один из файлов недоступен.Выясним причину неудачи автора при попытке использовать ВыполнитьОбработкуОповещения("ПрочитатьДанные"...). В 1С этот метод не ставит задачу в очередь событий, а вызывает процедуру напрямую. Представьте это как обычный вызов функции, просто записанный другим синтаксисом. Поскольку основной поток выполнения еще не закончился (цикл продолжается), система просто выполняет ваш код и идет дальше по циклу, в то время как асинхронные события загрузки файлов всё еще ждут своей очереди в стеке событий операционной системы.
Чтобы "протолкнуть" выполнение метода в самый конец очереди, иногда применяют обработчик ожидания:
ПодключитьОбработчикОжидания("ПрочитатьДанные", 0.1, Истина);
Это заставляет платформу дождаться, пока стек вызовов полностью очистится (включая все завершившиеся асинхронные загрузки), и только потом выполнить чтение данных. Однако этот метод считается менее надежным, чем явное использование счетчиков или Async/Await.
Для надежного выполнения кода после асинхронных операций в 1С выберите подходящий метод в зависимости от вашей платформы:
Асинх и Ждать. Это самый современный и понятный стандарт.НачатьПомещениеФайловНаСервер).ОсталосьОбработать, передавая её через ДополнительныеПараметры в ОписаниеОповещения.Эти подходы гарантируют, что ваши данные будут прочитаны только тогда, когда все необходимые файлы физически окажутся на сервере и пройдут первичную обработку.