В процессе разработки на платформе «1С:Предприятие» часто возникают ситуации, когда необходимо приостановить выполнение кода (например, при ожидании освобождения файла другим приложением) и при этом информировать пользователя о состоянии процесса. Обычный пустой цикл для этих целей не подходит, так как он блокирует интерфейс и не позволяет плавно обновлять индикатор выполнения. Разберем подробно, как правильно организовать такой цикл, рассчитать шаг прогресса и избежать типичных ошибок.
Рассмотрим классический подход, предложенный в обсуждении. Суть заключается в том, чтобы внутри цикла Пока постоянно сравнивать текущее системное время с расчетным временем окончания. Для того чтобы индикатор обновлялся плавно (например, от 0 до 100 за 120 секунд), нам необходимо вычислить коэффициент приращения.
Проанализируем пример кода, который позволяет увеличивать значение индикатора пропорционально прошедшему времени:
НачДата = ТекущаяДата();
КонДата = НачДата + 120; // Ожидаем 120 секунд
Коэфф = (КонДата - НачДата) / 100; // Вычисляем, сколько секунд приходится на 1% шкалы
Сч = 1;
лТекДата = НачДата + Коэфф;
Пока ТекущаяДата() < КонДата Цикл
// Проверяем, наступил ли момент для обновления индикатора
Если ТекущаяДата() >= лТекДата Тогда
ФормаИндикатора.ЭлементыФормы.Индикатор.Значение = Сч;
Сч = Сч + 1;
лТекДата = ТекущаяДата() + Коэфф;
КонецЕсли;
// Важный момент для отзывчивости интерфейса
ОбработкаПрерыванияПользователя();
КонецЦикла;
В данном примере мы используем переменную Коэфф, чтобы определить интервал обновления. Если общее время ожидания составляет 120 секунд, а шкала индикатора имеет 100 делений, то каждое деление будет заполняться примерно раз в 1.2 секунды.
Как справедливо отмечено в ходе обсуждения, использование «плотных» циклов (busy loop) — не самое технологичное решение. Оно создает 100% нагрузку на ядро процессора и может привести к зависанию окна приложения («Программа не отвечает»). Более правильным методом для клиентского приложения является использование встроенного механизма платформы — ПодключитьОбработчикОжидания.
Разберем по шагам, как это реализовать:
Пример реализации в модуле формы:
Перем мВремяНачала;
Перем мСекундОжидания;
Процедура НачатьОжидание()
мВремяНачала = ТекущаяДата();
мСекундОжидания = 120;
ПодключитьОбработчикОжидания("ВыполнитьПроверкуДоступности", 1);
КонецПроцедуры
Процедура ВыполнитьПроверкуДоступности() Экспорт
ПрошлоСекунд = ТекущаяДата() - мВремяНачала;
Если ПрошлоСекунд >= мСекундОжидания Тогда
ОтключитьОбработчикОжидания("ВыполнитьПроверкуДоступности");
Сообщить("Время ожидания истекло");
Возврат;
КонецЕсли;
// Обновляем индикатор (приводим к 100-балльной шкале)
ЭлементыФормы.Индикатор.Значение = (ПрошлоСекунд / мСекундОжидания) * 100;
// Здесь можно вставить проверку условия (например, попытку открытия файла)
КонецПроцедуры
При реализации механизмов ожидания необходимо учитывать архитектурные особенности 1С, о которых часто забывают начинающие разработчики. Выясним основные причины возможных проблем.
1. Разделение контекста Клиент-Сервер
В сообщении 13 упоминается, что логика работы с файлами находится в серверном модуле. Это критично: на сервере невозможно управлять элементами формы или вызывать ПолучитьОбщуюФорму. Если ожидание происходит в серверном вызове, индикатор на клиенте не сдвинется с места до тех пор, пока сервер не вернет управление. Правильная архитектура предполагает, что сервер выполняет быструю проверку и возвращает результат «занято», а цикл ожидания и управление формой индикатора происходят на стороне клиента.
2. Проблема «замораживания» интерфейса
Если вы все же используете цикл Пока, обязательно вставляйте команду ОбработкаПрерыванияПользователя(). Она позволяет платформе обработать очередь системных сообщений (перерисовка окна, нажатия кнопок). Без этой команды пользователь увидит белый экран вместо индикатора.
3. Снижение нагрузки на процессор (Sleep)
Пустой цикл в 1С потребляет все доступные ресурсы процессора. Чтобы «усыпить» поток на долю секунды, опытные разработчики используют вызовы внешних утилит или COM-объектов. Посмотрим на пример использования системной команды:
// Приостановка выполнения на 1 секунду без загрузки CPU
WshShell = Новый COMОбъект("WScript.Shell");
WshShell.Run("ping 127.0.0.1 -n 2", 0, Истина);
Команда ping с параметром -n 2 выполняется примерно 1 секунду. Параметр Истина заставляет 1С ждать завершения этой команды, тем самым реализуя безопасную паузу.
4. Точность расчетов
Функция ТекущаяДата() имеет дискретность в 1 секунду. Если вам нужна более высокая точность или плавность (например, обновление 10 раз в секунду), следует использовать функцию ТекущаяУниверсальнаяДатаВМиллисекундах(). Это позволит высчитывать прогресс более детально, избегая резких скачков индикатора.
Чтобы избежать ошибок деления на ноль и корректно обрабатывать любые интервалы, используйте универсальную формулу расчета процента:
ЗначениеИндикатора = (ТекущийМомент - ВремяНачала) / (ВремяОкончания - ВремяНачала) * 100
Такой подход гарантирует, что к моменту завершения цикла индикатор всегда будет равен ровно 100%, независимо от того, сколько секунд длилось ожидание и какой был шаг итерации.
Подводя итог, отметим: для простых обработок в «обычных формах» допустимо использование цикла с ТекущаяДата() и обязательной ОбработкаПрерыванияПользователя(). Однако для профессиональной разработки в управляемом приложении (v8.3) лучшим выбором будет использование длительных операций из состава БСП (Библиотека стандартных подсистем), которые работают в фоновом задании и не блокируют интерфейс пользователя.