Как дождаться инициализации переменной на клиенте 1С, не блокируя интерфейс?

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

Часто при работе с внешним оборудованием или выполнении длительных фоновых операций возникает задача: запустить процесс и дождаться его завершения, прежде чем продолжить выполнение кода. Основная проблема заключается в том, как организовать такое ожидание на клиенте, чтобы не "заморозить" пользовательский интерфейс. Рассмотрим типичную ситуацию: в клиентской процедуре нужно дождаться инициализации переменной (например, объекта компоненты), при этом ожидание не должно превышать заданный лимит времени, скажем, 8 секунд.

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

Неправильные подходы: чего следует избегать

Первая мысль, которая может прийти в голову, — это использовать цикл, который будет постоянно проверять состояние переменной. Давайте посмотрим, почему это плохая идея.

Вариант 1: Бесконечный цикл с проверкой

Код, подобный этому, кажется логичным, но он губителен для клиентского приложения:


// ЭТОТ КОД БЛОКИРУЕТ ИНТЕРФЕЙС! ИСПОЛЬЗОВАТЬ НЕЛЬЗЯ!
ВремяНачалаОжидания = ТекущаяУниверсальнаяДатаВМиллисекундах();
Пока МояКомпонента = Неопределено Цикл
    // Проверка таймаута
    Если ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачалаОжидания > 8000 Тогда
        Сообщить("Компонента не инициализирована, попробуйте позже");
        Прервать;
    КонецЕсли;
КонецЦикла;

Почему это не работает? Клиентское приложение 1С работает в одном потоке. Когда исполняется этот цикл, он полностью захватывает процессорное время, выделенное потоку. В результате программа перестает обрабатывать любые действия пользователя: нажатия кнопок, перемещение окна, даже обновление экрана. Для пользователя приложение просто "зависает" на 8 секунд. Это абсолютно недопустимое поведение.

Вариант 2: Пауза через COM-объекты

Иногда для создания паузы пытаются использовать внешние скрипты через COM, например, `WScript.Shell`.


// ЭТОТ КОД ТАКЖЕ БЛОКИРУЕТ ИНТЕРФЕЙС!
Процедура глПауза(Сек)
    scr = Новый COMОбъект("WScript.Shell");
    // Команда sleep приостанавливает выполнение кода
    scr.Run("sleep " + СокрЛП(Число(Сек)), 0, 1);
КонецПроцедуры;

Такой подход также является блокирующим. Вызов `scr.Run` с параметром, заставляющим ожидать завершения, останавливает выполнение кода 1С до тех пор, пока не отработает внешняя команда. Результат тот же — зависший интерфейс.

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

Правильное решение: использование обработчика ожидания

Для решения подобных задач в платформе 1С существует специальный асинхронный механизм — обработчик ожидания. Его суть в том, чтобы не "ждать" в одной процедуре, а настроить периодический вызов другой процедуры, которая будет проверять состояние.

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

Рассмотрим пошаговый алгоритм реализации.

Пошаговая реализация с тайм-аутом

Наша задача: запустить инициализацию компоненты, подождать до 8 секунд, и если она готова — продолжить работу, если нет — сообщить об ошибке.

Шаг 1. Подготовка формы

Для контроля времени ожидания нам понадобится переменная. Лучше всего сделать ее реквизитом формы, чтобы ее значение сохранялось между вызовами обработчика. Добавим на форму реквизит ВремяОжиданияСекунд с типом `Число`.

Шаг 2. Запуск процесса ожидания

Создадим команду, которая будет инициировать весь процесс. Например, `КомандаПечать`.


&НаКлиенте
Процедура КомандаПечать(Команда)

    // Здесь вызывается метод, который асинхронно начинает инициализацию компоненты
    // Например, он работает с внешним оборудованием и не возвращает результат сразу.
    Если Не ОбщийМодульРаботаСОборудованием.ПроинициализироватьМоюКомпоненту() Тогда
        Возврат;
    КонецЕсли;

    // Устанавливаем тайм-аут в секундах
    ВремяОжиданияСекунд = 8;
    
    // Подключаем обработчик, который будет вызываться каждую секунду.
    // Последний параметр "Ложь" означает, что обработчик будет вызываться многократно.
    ПодключитьОбработчикОжидания("ПроверитьИнициализациюКомпоненты", 1, Ложь);

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

Важный момент: после вызова ПодключитьОбработчикОжидания выполнение процедуры КомандаПечать завершается. Код не останавливается и не ждет. Интерфейс остается полностью доступным для пользователя. Вся дальнейшая логика будет выполняться внутри обработчика.

Шаг 3. Процедура-обработчик

Теперь создадим саму процедуру, которая будет вызываться каждую секунду и проверять состояние нашей компоненты.


&НаКлиенте
Процедура ПроверитьИнициализациюКомпоненты()

    // Блок 1: Проверка таймаута
    Если ВремяОжиданияСекунд < 1 Тогда
        // Время вышло, отключаем обработчик, чтобы он больше не вызывался
        ОтключитьОбработчикОжидания("ПроверитьИнициализациюКомпоненты");
        
        // Сообщаем пользователю о проблеме
        ТекстСообщения = НСтр("ru = 'Не удалось инициализировать компоненту оборудования.'");
        ОбщегоНазначенияКлиент.СообщитьПользователю(ТекстСообщения);
        
    // Блок 2: Проверка состояния компоненты
    // Предположим, что инициализированная компонента попадает в глобальную структуру
    ИначеЕсли Не ГлобальныеКомпоненты.Свойство("МояКомпонента") Тогда
        // Компонента еще не готова. Просто уменьшаем счетчик и ждем следующего вызова.
        ВремяОжиданияСекунд = ВремяОжиданияСекунд - 1;
        
    // Блок 3: Успешное завершение
    Иначе
        // Компонента готова!
        // Первым делом отключаем обработчик, он нам больше не нужен.
        ОтключитьОбработчикОжидания("ПроверитьИнициализациюКомпоненты");
        
        // Теперь можно вызывать процедуру, которая будет работать с компонентой
        ВыполнитьПечать();
    КонецЕсли;

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

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

Шаг 4. Продолжение работы

Наконец, процедура, которая должна была выполниться после ожидания. Она вызывается из обработчика в случае успеха.


&НаКлиенте
Процедура ВыполнитьПечать()

    // Получаем доступ к нашей компоненте
    МояКомпонента = ГлобальныеКомпоненты.МояКомпонента;
    
    // Выполняем необходимые действия
    МояКомпонента.Метод4();
    // ... и так далее

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

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

Современная альтернатива: асинхронность с Асинх/Ждать

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

Если ваша версия платформы это поддерживает, задача может быть решена элегантнее.


&НаКлиенте
Асинх Процедура КомандаПечать(Команда)

    // Запускаем асинхронную функцию, которая вернет "Обещание"
    Результат = Ждать ОжидатьИнициализациюКомпонентыАсинх(8);

    Если Результат.Статус = "Успех" Тогда
        // Компонента готова, работаем с ней
        Результат.Компонента.Метод4();
    Иначе
        // Таймаут или ошибка
        Сообщить("Не удалось инициализировать компоненту за отведенное время.");
    КонецЕсли;

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

&НаКлиенте
Асинх Функция ОжидатьИнициализациюКомпонентыАсинх(ТаймаутСекунд)

    ВремяНачала = ТекущаяУниверсальнаяДатаВМиллисекундах();
    ИнтервалПроверки = 200; // Проверяем каждые 200 мс

    Пока Истина Цикл
        // Проверяем, готова ли компонента
        Если ГлобальныеКомпоненты.Свойство("МояКомпонента") Тогда
            Возврат Новый Структура("Статус, Компонента", "Успех", ГлобальныеКомпоненты.МояКомпонента);
        КонецЕсли;

        // Проверяем таймаут
        Если ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачала > ТаймаутСекунд * 1000 Тогда
            Возврат Новый Структура("Статус", "Таймаут");
        КонецЕсли;

        // Делаем асинхронную паузу, чтобы не загружать процессор
        ОбещаниеПаузы = Новый Обещание(ПроцедураОповещения(ИнтервалПроверки));
        Ждать ОбещаниеПаузы;
    КонецЦикла;

КонецФункции

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

← На главную