В процессе разработки на платформе 1С:Предприятие часто возникает необходимость приостановить выполнение программного кода на определенное время. Такая потребность регулярно встречается при интеграции со сторонними сервисами (например, чтобы соблюдать лимиты на количество запросов в секунду к внешнему API), при организации многопоточной обработки данных, распределении нагрузки на сервер при пакетном проведении документов или при проектировании фоновых заданий, работающих в режиме демона. Исторически в платформе 1С отсутствовал простой и универсальный кроссплатформенный метод Sleep, что вынуждало разработчиков изобретать различные обходные пути.
В рамках этой статьи мы подробно разберем все существующие способы реализации задержки выполнения кода без использования внешних компонент и COM-объектов. Мы проанализируем как классические «костыли», так и современные штатные механизмы платформы 1С, выявим их достоинства, критические недостатки и особенности применения в различных операционных системах.
Начнем с самой важной и долгожданной новости для всех разработчиков 1С. Начиная с версии платформы 8.3.25, фирма «1С» добавила полноценный встроенный метод для остановки текущего потока выполнения.
Синтаксис этого метода крайне прост:
ВызватьПаузу(<Интервал>)
Параметр Интервал передается в миллисекундах (например, чтобы приостановить выполнение кода на 5 секунд, необходимо передать значение 5000).
Однако при использовании данного метода необходимо учитывать одно важнейшее архитектурное ограничение платформы: метод доступен только на стороне сервера (в фоновых заданиях, регламентных заданиях, Web- и HTTP-сервисах и т. д.). Более того, разработчики платформы заблокировали возможность прямого клиент-серверного вызова для этого метода. Если вы попытаетесь вызвать с клиента серверную процедуру, внутри которой будет выполняться ВызватьПаузу, система сгенерирует исключение:
«Нельзя вызвать метод ВызватьПаузу в клиент-серверном вызове»
Это сделано осознанно, чтобы предотвратить случайное «замораживание» пользовательского интерфейса при синхронных вызовах сервера. Таким образом, использовать данный метод можно исключительно в чисто серверном контексте, например, в фоновом задании:
&НаСервере
Процедура ВыполнитьОбработкуДанныхВФоне()
Для Каждого ПакетДанных Из СписокПакетов Цикл
ОбработатьПакет(ПакетДанных);
// Приостанавливаем поток на 3 секунды перед обработкой следующего пакета
ВызватьПаузу(3000);
КонецЦикла;
КонецПроцедуры
Если проект развернут на платформе версии ниже 8.3.25, но в конфигурации используется Библиотека стандартных подсистем (БСП), мы можем воспользоваться изящным и абсолютно безопасным решением, которое заложено в общем модуле ОбщегоНазначенияБТС (или аналогичных механизмах БСП) — процедурой Пауза(Секунд).
Принцип работы этого механизма построен на хитрости с фоновыми заданиями:
ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд), где в качестве целевого фонового задания передается оно само (или заведомо несуществующий идентификатор).Этот способ является наиболее надежным, стабильным и кроссплатформенным (одинаково успешно работает как под управлением Windows, так и под Linux) для версий платформ от 8.3.5 до 8.3.24.
Рассмотрим метод, предложенный автором темы на форуме. Он основан на попытке установить сетевое соединение с заведомо недоступным адресом и ожидании системного таймаута. Автор предложил использовать ИнтернетПочтовыйПрофиль и отправку запроса на SMTP-порт публичного DNS 8.8.8.8:
Процедура ПаузаSMTP(ДлительностьВСекундах) Экспорт
Если ДлительностьВСекундах > 0 Тогда
Попытка
Профиль = Новый ИнтернетПочтовыйПрофиль;
Профиль.АдресСервераSMTP = "8.8.8.8";
Профиль.Таймаут = ДлительностьВСекундах;
Профиль.ПортSMTP = 25;
Почта = Новый ИнтернетПочта;
Почта.Подключиться(Профиль);
Исключение
// Игнорируем ошибку неподключения по таймауту
КонецПопытки;
КонецЕсли;
КонецПроцедуры
Проанализируем этот алгоритм. Он действительно работает в некоторых средах, но имеет критические недостатки:
RST (Reset) или ошибку ICMP Port Unreachable, то исключение сгенерируется мгновенно, и паузы не произойдет.Гораздо более стабильным, быстрым и безопасным решением является использование объекта HTTPСоединение, направленного на локальный адрес обратной петли 127.0.0.1 (localhost) и заведомо свободный порт (например, 9999). Этот способ не требует обращения к внешнему интернету и не зависит от почтовых протоколов.
Рассмотрим пример реализации паузы через локальное HTTP-соединение:
Процедура ПаузаЧерезHTTP(КоличествоСекунд) Экспорт
Если КоличествоСекунд <= 0 Тогда
Возврат;
КонецЕсли;
Попытка
// Создаем соединение к неиспользуемому локальному порту с нужным таймаутом
Соединение = Новый HTTPСоединение("127.0.0.1", 9999, , , , КоличествоСекунд);
Соединение.Получить(Новый HTTPЗапрос());
Исключение
// Ошибка таймаута подключения ожидаема, мы ее просто перехватываем
КонецПопытки;
КонецПроцедуры
Этот метод работает чрезвычайно стабильно, не нагружает центральный процессор и функционирует как на Windows, так и на Linux.
Еще один распространенный в сообществе разработчиков подход — это вызов штатных консольных утилит операционной системы с помощью метода ЗапуститьПриложение. На форуме были предложены варианты использования PowerShell и ping.
Посмотрим на пример вызова задержки через PowerShell в Windows:
ЗапуститьПриложение("powershell -command ""& {Start-Sleep -Seconds 5}""", , Истина);
В данном вызове третий параметр Истина является ключевым — он заставляет платформу 1С приостановить выполнение текущего потока и ожидать завершения работы вызванного приложения (в данном случае консоли PowerShell, которая засыпает на 5 секунд).
Проанализируем особенности этого и смежных методов запуска внешних утилит:
timeout не будут работать на сервере под управлением Linux. Если планируется миграция на Linux, такой код придется переписывать.ping для организации паузы, то параметры вызова на разных ОС будут отличаться. В Windows количество пакетов задается ключом -n (ping 127.0.0.1 -n 5), а в Linux — ключом -c (ping 127.0.0.1 -c 5). Вам придется писать ветвление кода, анализируя тип платформы с помощью СистемнаяИнформация.ТипПлатформы.На форуме был упомянут еще один старый метод организации ожидания — пустой цикл со сравнением текущей даты:
// ВНИМАНИЕ: Данный код является вредоносным для серверных систем!
Процедура ПаузаЧерезЦикл(ДлительностьСекунд)
ВремяОкончания = ТекущаяДата() + ДлительностьСекунд;
Пока ТекущаяДата() < ВремяОкончания Цикл
// Пустой цикл
КонецЦикла;
КонецПроцедуры
Выясним причину, почему этот метод категорически запрещено использовать в реальных рабочих базах данных (особенно в клиент-серверной архитектуре).
При запуске пустого цикла процессор начинает выполнять миллиарды простейших операций сравнения в секунду без остановки. Это приводит к тому, что одно ядро процессора нагружается на 100%. Если на сервере терминалов или сервере приложений 1С несколько фоновых заданий или пользователей одновременно запустят такой код, сервер моментально израсходует все свободные вычислительные ресурсы. В результате это приведет к перегреву оборудования, падению производительности всей системы и зависанию сессий других пользователей. Никогда не используйте циклы ожидания на основе системного времени!
Мы подробно проанализировали ключевые методы создания паузы при выполнении кода в 1С. Подведем итоги и выберем оптимальные стратегии:
ВызватьПаузу(). Это наиболее производительное и нативное решение.ОбщегоНазначенияБТС.Пауза(). Он безопасен для процессора и полностью кроссплатформен.