Пауза и ожидание во встроенном языке 1С 8.3

· 9 мин

Содержание

Готового оператора Sleep, как в других языках, во встроенном языке 1С нет. Паузу собирают из того, что есть: на клиенте в управляемой форме это ПодключитьОбработчикОжидания, на сервере — цикл по таймеру с проверкой ОбработкаПрерыванияПользователя, а для долгого ожидания — фоновое задание и метод ОжидатьЗавершенияВыполнения.

Выбор метода зависит не от длительности паузы, а от того, где выполняется код. Клиентская и серверная пауза ведут себя по-разному: одна освобождает интерфейс, вторая держит занятым рабочий процесс кластера. Ниже — как сделать каждую и чем они обходятся.

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

Чтобы не держать все способы в голове, вот они рядом:

СпособГде работаетБлокирует потокКогда применять
Цикл по таймеруСервер и толстый клиентДаПауза в доли секунды
ПодключитьОбработчикОжиданияУправляемая форма (клиент)НетОтложенный или повторяющийся вызов
ОжидатьЗавершенияВыполненияСерверДа, до завершения заданияДождаться фоновой операции
Регламентное заданиеСерверНетЗапуск по расписанию
Таймаут соединенияСервер и клиентНет (ограничивает ожидание)Обращение к внешнему сервису

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

На сервере приостановить выполнение можно только одним надёжным способом — крутить пустой цикл, сверяясь с таймером. Текущее время в миллисекундах даёт функция ТекущаяУниверсальнаяДатаВМиллисекундах():

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

Пока (ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало) < ПаузаМс Цикл
    ОбработкаПрерыванияПользователя();
КонецЦикла;

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

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

Для измерения интервала берите именно ТекущаяУниверсальнаяДатаВМиллисекундах(), а не ТекущаяДата(). Первая возвращает число миллисекунд и не зависит от часового пояса сеанса, вторая даёт точность только до секунды — для коротких пауз этого мало, а при переводе часов результат может «скакнуть».

Иногда советуют вызывать Sleep из Windows через COM-объект. Способ работает только на Windows, непредсказуем в клиент-серверном варианте и на сервере его лучше не применять. Если пауза нужна именно на сервере и она дольше пары секунд — это сигнал, что задачу пора решать заданием, а не паузой.

Пауза на клиенте без заморозки формы

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

&НаКлиенте
Процедура ПриОткрытииНаСервере(...)
    // ...
КонецПроцедуры

&НаКлиенте
Процедура ПриОткрытии(Отказ)
    ПодключитьОбработчикОжидания("ОбновитьСтатус", 5);
КонецПроцедуры

&НаКлиенте
Процедура ОбновитьСтатус()
    // код выполняется каждые 5 секунд
КонецПроцедуры

Форма при этом остаётся живой: пользователь работает, а обработчик срабатывает в паузах между действиями.

Интервал задаётся в секундах. Для целого значения минимум — 1 секунда; дробный интервал (например, 0,5) поддерживается в актуальных версиях платформы, минимально допустимое дробное значение — порядка 0,2 секунды. Задать паузу в 50 миллисекунд через обработчик нельзя.

Пара важных ограничений:

  • Подключить обработчик можно только из клиентского контекста управляемого приложения. Из серверной процедуры вызвать ПодключитьОбработчикОжидания не получится — паузу на сервере так не сделать.
  • На одно имя процедуры действует один обработчик. Повторный вызов ПодключитьОбработчикОжидания с тем же именем переопределяет интервал, а не добавляет второй таймер.
  • Интервал — это пауза между вызовами, а не жёсткий период. Если предыдущий вызов ещё выполняется или форма занята, следующее срабатывание сместится.

Разовый таймер и его отключение

По умолчанию обработчик ожидания повторяется бесконечно. Чтобы он сработал один раз — отложенный запуск после открытия формы, — третьим параметром передают Истина:

// сработает ровно один раз через 1 секунду
ПодключитьОбработчикОжидания("ПоказатьПодсказку", 1, Истина);

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

ОтключитьОбработчикОжидания("ОбновитьСтатус");

Типичный сценарий с отключением — ждать завершения фоновой операции и прекратить опрос, как только она закончилась:

  1. При открытии формы подключить обработчик с интервалом 2–3 секунды.
  2. В обработчике проверить состояние задания на сервере.
  3. Когда задание завершилось — вызвать ОтключитьОбработчикОжидания и показать результат.

Имя процедуры в ОтключитьОбработчикОжидания должно совпадать с тем, что передали при подключении, иначе таймер останется активным и продолжит дёргать сервер.

В современной платформе (8.3.18 и новее) есть асинхронные методы с ключевыми словами Асинх и Ждать — они позволяют не блокировать интерфейс при вызове диалогов и длительных операций. Но отдельной асинхронной «паузы» среди них нет: для отложенного по времени вызова в форме по-прежнему используют обработчик ожидания. Асинхронные методы решают задачу «не заморозить форму на время ожидания ответа», а не «подождать N секунд».

Прерывание долгого цикла пользователем

Отдельная задача — не пауза, а возможность остановить затянувшийся расчёт. За это отвечает ОбработкаПрерыванияПользователя(). Метод проверяет, не нажал ли пользователь прерывание (на толстом клиенте — Ctrl+Break), и если нажал — прерывает выполнение.

Ставьте этот вызов внутри любого длинного цикла — и обработки больших выборок, и паузы по таймеру:

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    ОбработкаПрерыванияПользователя();
    // обработка строки
КонецЦикла;

Без этого вызова долгая операция выглядит как зависшая: пользователь не может её отменить, а платформа не отдаёт управление. Это дешёвая страховка — одна строка в теле цикла.

Где и зачем нужна пауза?

Ожидание завершения фонового задания

Когда нужно запустить длительную операцию и дождаться её результата, паузу заменяют фоновым заданием. Основной поток не крутит пустой цикл, а ждёт конкретное событие — завершение задания.

Задание = ФоновыеЗадания.Выполнить(
    "МойОбщийМодуль.ДлительнаяОперация", , , "Пересчёт итогов");

// ждать завершения, но не дольше 30 секунд
Задание.ОжидатьЗавершенияВыполнения(30);

Если Задание.Состояние = СостояниеФоновогоЗадания.Завершено Тогда
    // забрать результат
КонецЕсли;

Метод ОжидатьЗавершенияВыполнения блокирует вызывающий поток до завершения задания или до истечения указанного интервала (в секундах). Если задание не успело закончиться за отведённое время — выполнение продолжится, а состояние останется «Выполняется».

Прежнее название метода — ОжидатьЗавершения — считается устаревшим; в новом коде используйте ОжидатьЗавершенияВыполнения.

Полноценный неблокирующий сценарий выглядит так. Клиент запускает фоновое задание на сервере и получает его идентификатор. Затем на клиенте подключается обработчик ожидания с интервалом 2–3 секунды. На каждом срабатывании клиент коротким серверным вызовом проверяет состояние задания по идентификатору. Как только состояние стало «Завершено» или «Отменено» — обработчик отключается и форма показывает результат. Основной поток при этом нигде не висит в цикле.

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

На клиенте связка «запустить фоновое задание + опрос через обработчик ожидания» предпочтительнее серверного ожидания: интерфейс остаётся отзывчивым, а сервер не держит лишний сеанс в режиме ожидания.

Отложенный запуск вместо паузы: регламентные задания

Если задача звучит как «выполнить код через 10 минут» или «каждую ночь», пауза не нужна вовсе — нужен планировщик. В платформе это регламентные задания с расписанием.

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

Два важных отличия по вариантам работы:

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

Регламентное задание — правильная замена длинной серверной паузе: оно не занимает рабочий процесс в промежутках и переживает перезапуск сервера.

Таймауты соединений и обработка ошибок

Отдельный класс «ожиданий» — обращения наружу: HTTP-сервисы, веб-сервисы, внешние источники данных. Здесь пауза не задаётся вручную, но важно ограничить время ожидания ответа, иначе поток зависнет на недоступном адресе.

У HTTPСоединение в конструкторе есть параметр таймаута (в секундах) — задавайте его явно, не полагаясь на бесконечное ожидание. Любой сетевой вызов оборачивайте в Попытка, чтобы обрыв связи не ронял всю обработку:

Попытка
    Ответ = Соединение.Получить(Запрос);
Исключение
    Сообщить("Сервис недоступен: " + ОписаниеОшибки());
    // обработать таймаут/ошибку соединения
КонецПопытки;

Текст исключения разбирают через ИнформацияОбОшибке() или ОписаниеОшибки(). Таймаут соединения приходит сюда же как исключение — отдельного «кода ожидания» платформа не возвращает.

Таймаут задаётся не везде одинаково:

  • у HTTPСоединение — параметром конструктора;
  • у веб-сервисных и WSDL-соединений — своими настройками прокси и подключения;
  • у COMОбъект управляемого таймаута ожидания, как правило, нет — зависший внешний вызов COM прервать штатно сложно, поэтому к COM на сервере относятся осторожно.

Разумное время ожидания — несколько секунд для внутренней сети и 30–60 секунд для внешних сервисов. Бесконечное ожидание (таймаут не задан) опасно: один недоступный адрес способен подвесить рабочий процесс.

Таймаут соединения и пауза в коде — разные вещи. Таймаут ограничивает ожидание ответа извне, а не приостанавливает выполнение по вашей команде.

Как пауза влияет на производительность кластера

Любая серверная пауза — это занятый рабочий процесс rphost. Чем дольше и чаще такие паузы, тем меньше у кластера свободных ресурсов на реальную работу.

Что происходит под капотом при пустом цикле ожидания на сервере:

  • 🔥 одно ядро процессора загружено вхолостую всё время паузы;
  • 🔒 если пауза внутри транзакции — блокировки держатся дольше, растёт риск конфликтов и таймаутов ожидания блокировок у соседних сеансов;
  • 🧮 активный сеанс занимает лицензию и слот в кластере;
  • ⏳ при десятках таких пауз одновременно рабочие процессы упираются в потолок, остальные пользователи ждут.

Отсюда практические выводы:

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

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

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

Частые вопросы

Есть ли в 1С оператор Sleep, как в других языках?
Нет. Паузу собирают из подручных средств: на клиенте — обработчик ожидания, на сервере — цикл по ТекущаяУниверсальнаяДатаВМиллисекундах(). Прямого оператора приостановки потока во встроенном языке не предусмотрено.
Почему обработчик ожидания не вызывается чаще раза в секунду?
Для целого интервала минимум — 1 секунда. Дробные интервалы (меньше секунды) поддерживаются в актуальных версиях платформы, с нижней границей порядка 0,2 секунды. Задать интервал в миллисекундах нельзя.
Зависнет ли форма на время паузы?
Обработчик ожидания форму не блокирует — пользователь продолжает работать. А вот пустой цикл на сервере блокирует вызвавший его поток целиком, поэтому в клиентском коде управляемой формы такой цикл писать нельзя.
Как прервать зависший цикл с паузой?
Внутри цикла должен вызываться ОбработкаПрерыванияПользователя(). Тогда на толстом клиенте операцию можно оборвать сочетанием Ctrl+Break. Без этого вызова прервать выполнение штатно не получится.
Чем дождаться окончания долгой операции, не крутя цикл?
Запустите её фоновым заданием и вызовите ОжидатьЗавершенияВыполнения с таймаутом в секундах. Метод вернёт управление, как только задание завершится или истечёт указанное время.