О бедной паузе замолвите слово (Дополнено)

06.09.24

Разработка - БСП (Библиотека стандартных подсистем)

А что, если долгожданная реализация Паузы в 1С смутно напоминает старую, проверенную? А?!

Глава 1. Расследование

Глава 2. Постскриптум

Я совсем начинающий программист, но уже пару раз приходилось искать как вызвать паузу в 1С и чего я только не видел: внешние компоненты, powershell, ping .... Ну, главное, чего от них требовалось, они делали - пауза срабатывала! С выходом платформы 8.3.25 я обрадовался, несмотря на общее разочарование, переписал паузу на такой набор функций:

Процедура Пауза2_0(Секунд, НужнаТочность = Ложь) Экспорт
	Интервал = Секунд * 1000;
	Если НужнаТочность Тогда
		Интервал = Интервал - 110; // примерно столько добавляет вызов задания на моей "тачке"
	КонецЕсли;
	ПараметрыЗадания = Новый Массив;
	ПараметрыЗадания.Добавить(Интервал);
	ФЗ = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.НуВызватьПаузу", ПараметрыЗадания);
	ФЗ.ОжидатьЗавершенияВыполнения();

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

Процедура НуВызватьПаузу(Длительность) Экспорт
	ВызватьПаузу(Длительность); //Хотели, чтобы мы вызывали это только в фоновых заданиях - пожалуйста!
КонецПроцедуры

А потом мне понадобилось залезть в дебри надстройки Битрикс (я на обычных формах работаю, здесь не расширение) и я заметил интересную функцию Таймаут. Мне стало интересно, как разработчики Битрикс реализовали паузу. Я посмотрел и вижу вызов в фоновом задании функции "ОбщегоНазначенияБТС.Пауза". Постойте, так реализация паузы уже есть в БСП? Ну-ка, ну-ка: 

Процедура Пауза(Секунд) Экспорт
    ТекущийСеансИнформационнойБазы = ПолучитьТекущийСеансИнформационнойБазы();
    ФоновоеЗадание = ТекущийСеансИнформационнойБазы.ПолучитьФоновоеЗадание(); //если вызвать из фонового задания, возвращает ссылку на него
	
    Если ФоновоеЗадание = Неопределено Тогда // мы еще не в фоновом задании, делаем небольшую рекурсию
        Параметры = Новый Массив;
        Параметры.Добавить(Секунд);
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("ОбщегоНазначенияБТС.Пауза", Параметры);
    КонецЕсли;
		
    Попытка
        ФоновоеЗадание.ОжидатьЗавершения(Секунд);  //Тут совершается магия
    Исключение
        Возврат;
    КонецПопытки;
КонецПроцедуры

Так, непонятно, как так получается, что двойной вызов ОжидатьЗавершения (или рекомендуемой теперь ОжидатьЗавершенияВыполнения) приводит к паузе ровно на время ожидания? А оказывается просто: ОжидатьЗавершенияВыполнения, вызванный внутри фонового задания приводит к банальной паузе по таймауту, и следующий код в фоновом задании также приведет к паузе:

    ЗаписьЖурналаРегистрации("Доработки. Вай мама", УровеньЖурналаРегистрации.Информация, , , "1");
    ТекущийСеансИнформационнойБазы = ПолучитьТекущийСеансИнформационнойБазы();
    ФоновоеЗадание = ТекущийСеансИнформационнойБазы.ПолучитьФоновоеЗадание();
    ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд);
    ЗаписьЖурналаРегистрации("Доработки. Вай мама", УровеньЖурналаРегистрации.Информация, , , "2");

Приведет к записям в журнале:

Выходит, пауза, которую нам так радостно презентовали уже давно была что ли?

Вопрос. Зачем они это сделали? Они просто сказать не могли что ли, или они сами не ожидали, что ОжидатьЗавершенияВыполнения так заработает и решили подстраховаться, вдруг баг, ставший фичей, прекратит свое существование?

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

Процедура Пауза(Секунд) Экспорт
    ТекущийСеансИнформационнойБазы = ПолучитьТекущийСеансИнформационнойБазы();
    ФоновоеЗадание = ТекущийСеансИнформационнойБазы.ПолучитьФоновоеЗадание();
    
    Если ФоновоеЗадание = Неопределено Тогда
		Параметры = Новый Массив;
        Параметры.Добавить(Секунд);
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.Пауза", Параметры);
        ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд); 
    Иначе
		Интервал = Цел(Секунд * 1000);
		Если Интервал > 0 Тогда
			ВызватьПаузу(Интервал);
		КонецЕсли;
    КонецЕсли;
КонецПроцедуры

 

P.S. Нееет, такую тему как пауза так просто оставлять нельзя, поколения разработчиков кустарных методов мне этого не простят, да и мне странно. ПОдумаешь, пауза! В общем, я решил некоим образом почтить память изысканных велосипедостроителей, подаривших мне и другим самые разные способы паузы в 1С и придумать свои, естественно, не выходящие уже за рамки стандартного кода 1С и исключительно ее посредством (для кросс-платформенности, например). Естественно, в меру своих способностей, так что уж прошу не обессудьте!

1. Первая пауза наводит порядок в методах БСП. А там проблема - используется нерекомендуемый метод! Устраняем и при этом сохраняем совместимость со старыми платформами (ведь не все перешли на 8.3.25)

Процедура ПаузаЛегаси(Секунд) Экспорт
    ТекущийСеансИнформационнойБазы = ПолучитьТекущийСеансИнформационнойБазы();
    ФоновоеЗадание = ТекущийСеансИнформационнойБазы.ПолучитьФоновоеЗадание();
    
    Если ФоновоеЗадание = Неопределено Тогда
        Параметры = Новый Массив;
        Параметры.Добавить(Секунд);
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.ПаузаЛегаси", Параметры);
    КонецЕсли;
    //Уходим от использования нерекомендуемых методов
    ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд);
    
КонецПроцедуры

2. Вторая, заставляет призадуматься на тему, а что если пауза в нашем приложении такая же частая гостья, как Обработка проведения или хотя бы СохранитьЗначение? Даешь паузу для Всех!

Процедура ПаузаОптовик(Секунд) Экспорт
    // Здравствуйте, уважаемый, у Вас курить есть?
    УстановитьПривилегированныйРежим(Истина);
    ЗаданияКурить = ФоновыеЗадания.ПолучитьФоновыеЗадания(Новый Структура("Состояние, Наименование", СостояниеФоновогоЗадания.Активно, "Курить"));
    Если ЗаданияКурить.Количество() > 0 Тогда
        // отгрузите мне немного
        ЗаданияКурить[0].ОжидатьЗавершенияВыполнения(Секунд);
    Иначе
        //запрашиваю новую поставку!
        ФоновоеЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
        Если ФоновоеЗадание <> Неопределено И Найти(ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание().ИмяМетода, "ПаузаОптовик") Тогда
            //вы уже запрашивали, поставка сейчас НЕ-ВОЗ-МО-ЖНА, подождите (было, что забыл указать наименование задания, 3000 заданий - не предел! Да и мало ли)
            ВызватьПаузу(Секунд * 1000);
            Возврат;
        Иначе
            Параметры = Новый Массив;
            Параметры.Добавить(10800);
            ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.ПаузаОптовик", Параметры, , "Курить");
            ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд);
        КонецЕсли;
    КонецЕсли;
    УстановитьПривилегированныйРежим(Ложь);
КонецПроцедуры

При этом, что любопытно, ограничение на ожидание фонового задания только своего пользователя или любого при наличии административных прав легко обходятся получением привилегированного режима, поэтому не важно, под каким пользователем запущена пауза, все смогут ей пользоваться!

3. Следующий метод использует ОжидатьЗавершенияВыполнения на полную катушку. Ведь если задуматься, у нас на рабочих базах постоянно выполняются какие-то фоновые задания. А что если их тормознуть и стрельнуть парочку секунд (без ущерба для них, разумеется, мы ж не звери какие):

Процедура ПаузаХулиганская(Секунд) Экспорт
    ОтметкаВремени = ТекущаяУниверсальнаяДатаВМиллисекундах();
    Попытка
        Если Цел(Секунд * 1000) > 0 Тогда 
            //Курить есть?
            ВызватьПаузу(Цел(Секунд * 1000));
        КонецЕсли;
    Исключение
        // а если найду?
        УстановитьПривилегированныйРежим(Истина);
        СостоятельныеЗадания = ФоновыеЗадания.ПолучитьФоновыеЗадания(Новый Структура("Состояние", СостояниеФоновогоЗадания.Активно));
        Индекс = СостоятельныеЗадания.Количество();
        СколькоЕщеКурить = Секунд * 1000 - ТекущаяУниверсальнаяДатаВМиллисекундах() + ОтметкаВремени;
        Пока СколькоЕщеКурить > 0 Цикл //так, а у Вас что?
            Индекс = Индекс - 1;
            Если Индекс < 0 Тогда
                // курим свои, чего уж там
                Параметры = Новый Массив;
                Параметры.Добавить(Секунд);
                ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.ПаузаХулиганская", Параметры);
                ФоновоеЗадание.ОжидатьЗавершенияВыполнения(СколькоЕщеКурить / 1000);
            Иначе
                // ага, а это что?
                СостоятельныеЗадания[Индекс].ОжидатьЗавершенияВыполнения(СколькоЕщеКурить / 1000);
            КонецЕсли;
            СколькоЕщеКурить = Секунд * 1000 - ТекущаяУниверсальнаяДатаВМиллисекундах() + ОтметкаВремени;    
        КонецЦикла;
        УстановитьПривилегированныйРежим(Ложь);
    КонецПопытки;
КонецПроцедуры

Лично для себя я сделал вывод, что планирование ночных заданий можно снабдить новым инструментом: планирование последовательности выполнения заданий без необходимости сращивать их жестко, как и "угадывать", когда закончится предыдущее задание - можно просто задать в меру своей фантазии ключи или наименования к заданиям, которые будут регулировать очередность и приоритет их выполнения - здорово! Также, обратите внимание: новый метод паузы, в отличие от старого не умеет обрабатывать ни отрицательных чисел, ни чисел с дробной частью. Просто выпадает в ошибку, поэтому даже в таком раздолбайском методе приходится добавлять такие скучные проверки (очкариков не бьем, беременных не спрашиваем)

4. А, вы еще здесь? Тогда добавим в копилку паузу, использующую новый метод и основанный на доверии:

Процедура ПаузаПравославная(Секунд, УжеВФоновомЗадании = Ложь) Экспорт
    Если УжеВФоновомЗадании = Ложь Тогда
        Параметры = Новый Массив;
        Параметры.Добавить(Секунд);
        Параметры.Добавить(Истина);
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.ПаузаПравославная", Параметры);
        ФоновоеЗадание.ОжидатьЗавершенияВыполнения(Секунд);
    Иначе
         ВызватьПаузу(Цел(Секунд * 1000));
       КонецЕсли;
КонецПроцедуры

Все просто - в фоновом запускаем вызов паузы, в клиент-серверном режиме (так пишет сама 1С в сообщении об ошибке) - вызываем фоновым себя же.

5. Ну и напоследок, странный метод, который старается выжать максимум точности (но у него получается хуже, чем у одного из вышестоящих методов, угадайте какого):

Процедура ПаузаСионисткая(Секунд) Экспорт
    //Говорят, Моисей 40 лет водил евреев по пустыне, прежде чем они обрели свою родину, сколько у вас бродят фоновые задания, прежде чем начать заняться делом, тоже неизвестно
    ОтметкаВремени = ТекущаяУниверсальнаяДатаВМиллисекундах();
    ТекущийСеансИнформационнойБазы = ПолучитьТекущийСеансИнформационнойБазы();
    ФоновоеЗадание = ТекущийСеансИнформационнойБазы.ПолучитьФоновоеЗадание();
    
    Если ФоновоеЗадание = Неопределено Тогда
        ДанныеОтклонения = ХранилищеОбщихНастроек.Загрузить("СреднееОтклонениеПаузы");
        Параметры = Новый Массив;
        Параметры.Добавить(Секунд);
        ФоновоеЗадание = ФоновыеЗадания.Выполнить("ДополнительныеМеханизмы.ПаузаСионисткая", Параметры);
        НачальноеВремяОжидания = ?(ДанныеОтклонения = Неопределено, Секунд, Секунд - ДанныеОтклонения.ВремяОтклонения / ДанныеОтклонения.Количество / 500);
        НачальноеВремяОжидания = НачальноеВремяОжидания * 1000;
        ВремяОжидания = НачальноеВремяОжидания;
        ОтметкаВремени = ТекущаяУниверсальнаяДатаВМиллисекундах();
        Пока ВремяОжидания > 10 Цикл    
            ФоновоеЗадание.ОжидатьЗавершенияВыполнения(0.005);
            ВремяОжидания = НачальноеВремяОжидания - ТекущаяУниверсальнаяДатаВМиллисекундах() + ОтметкаВремени;
        КонецЦикла;
        СохранитьДанныеОтклонения(ОтметкаВремени, НачальноеВремяОжидания);
    Иначе
        Интервал = Цел(Секунд * 1000);
        Если Интервал > 0 Тогда
            ВызватьПаузу(Интервал);
        КонецЕсли;
    КонецЕсли;
КонецПроцедуры

Здесь для каждого пользователя ведется подробный учет того, сколько раз была запущена пауза и как долго ее пришлось ждать по сравнению с желаемым. Чтобы постараться выровнять время ожидания около точного времени, заданного параметром, вычитаем двойную меру среднего отклонения. А в остальном, обычная пауза, кто придумал, что это связано с евреями?

Ну и напоследок, думаю стоит показать Вам результаты моих замеров, ведь нельзя было сравнивать методы только по внешнему виду. Это не экзамен, конечно, но и чай, не на скамейке в парке на красивых девушек глазеем. Хочется отметить, что основная неточность вызова паузы в отдельном задании в том, что завершается оно почему-то не так быстро, как запускается, отсюда взялись мои 110 миллисекунд "для точности". Однако, здесь нам на выручку приходит с куда большей точностью работающий таймер отслеживания завершения - ОжидатьЗавершенияВыполнения (он к тому же и не такой требовательный к входным данным, корректно переваривает все, только буквы не подавал). Серьезных замеров не делал, чтобы меня не обвинили в поклонении паузе (упаси боже!), но не одну сотню раз по одной секунде у меня каждый метод успел отработать. И вот результат (в миллисекундах):

Зачем-то еще делал паузу на отдельной форме для того, чтобы блокировать только окно владельца, позволяя спокойно работать в это время с остальным интерфейсом, но кому я об этом рассказываю, никто же на 1С шахмат и морского боя не делает. Пока! Чао! До свиданья

P.P.S. По советам трудящихся сравнил замеры и для метода ожидания интернет-ресурса, естественно, недоступного.

Процедура ПаузаОтрицания(Секунд) Экспорт
    Соединение = Новый HTTPСоединение("127.0.0.0", , , , , Секунд);
    Попытка
        Соединение.Получить(Новый HTTPЗапрос()); 
    Исключение 
    КонецПопытки;
КонецПроцедуры

Как ни странно, точность у такого подхода на высоте:

Замеры времени при паузе

Причем в данном случае также захотелось сравнить и нагрузку на процессор во время паузы, вдруг требование ресурса отнимает у процессора драгоценное время, но нет и здесь все в порядке на время всех 8 методов (примерно по 2 минуты каждый):

Отдельные всплески не считаем, средняя нагрузка на бюджетный процессор под пользовательской ОС в пределах 2%. Особо плоское плато показывают Хулиганская, Оптовик и ... ПаузаОтрицания - факт! Но, конечно, ничего не мешает нам с умом пользоваться любой из пауз, если они, конечно, у вас есть!

Пауза в 1С Фоновые задания ОжидатьЗавершенияВыполнения

См. также

БСП (Библиотека стандартных подсистем) Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Пример отслеживания прогресса фонового выполнения дополнительной обработки с использованием программного интерфейса длительных операций БСП.

10.09.2024    990    MadRave    1    

12

БСП (Библиотека стандартных подсистем) Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Добавим дополнительные свойства в новый документ средствами БСП

02.09.2024    3535    John_d    10    

51

БСП (Библиотека стандартных подсистем) Программист Платформа 1С v8.3 Бесплатно (free)

Всё больше организаций выбирает для серверов под 1С операционные системы Linux. Одним из отличий систем Windows и Linux является отсутствие COM объектов, которые зачастую использовались для формирования печатных форм офисных документов (Word). Конечно, можно выполнять печать и на клиенте, но есть риск импортозамещения. В работе у меня случались проблемы с зависанием процесса Word, поэтому я не люблю его использовать.

29.07.2024    4603    PROSTO-1C    12    

49

БСП (Библиотека стандартных подсистем) Программист Платформа 1С v8.3 1С:Розница 3.0 Россия Бесплатно (free)

Описание возможности печати произвольного QR-кода в текстовом (не фискальном) документе ККМ с помощью типовых функций БПО.

22.07.2024    682    KirillZ44    6    

9

Инструментарий разработчика БСП (Библиотека стандартных подсистем) Программист Платформа 1С v8.3 Конфигурации 1cv8 Абонемент ($m)

Пример шаблона для многопоточного выполнения фонового задания на основе БСП. Шаблоны сделаны для процедуры и функции.

2 стартмани

03.05.2024    1653    25    Hitcher    3    

13
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. ixijixi 1901 07.09.24 10:49 Сейчас в теме
Выходит, пауза, которую нам так радостно презентовали уже давно была что ли?
Пауза была в БСП, теперь это метод платформы
https://wonderland.v8.1c.ru/blog/metod-vyzvatpauzu/

Правда, метод куцый, с кучей ограничений.
Метод ВызватьПаузу доступен только на сервере и может быть вызван из фоновых заданий, веб-сервисов, http-сервисов, ботов и т.п.

Важная особенность: метод ВызватьПаузу недоступен в клиент-серверном вызове
2. pbelousov 26 08.09.24 18:11 Сейчас в теме
Добрый день!
В зависимости от версии БСП, были разные паузы.
Сначала - работала не только в фоновом задании, но и в серверном модуле. в этом случае она автоматически создавала фоновое задание, и ожидала его завершения.
позже, - оставили только для фоновых заданий.
и это не просто так! запуск нового сеанса - тяжелая операция, и без надобности - не надо этим баловаться.

например, вы ставите паузу в цикл,
пишите что-то вроде: "дождь уже кончился? подождать ещё..."

так, бесполезным ожиданием в цикле мы "бомбим" создание новых сеансов.
n_mezentsev; +1 Ответить
6. n_mezentsev 56 11.09.24 11:01 Сейчас в теме
(2) И здесь вам поможет ПаузаОптовик) Да и честно я бы не назвал задержку в 6-7 миллисекунд серьезной нагрузкой. Я на Celeron N4000 отрабатывал паузу, даже там нагрузка на сервер 1С была несерьезной
8. pbelousov 26 11.09.24 11:35 Сейчас в теме
(6) тяжелая - не в милисекундах,
а в "разбазаривании" ресурсов. в цикле....
потому разработчики и убрали такую возможность, сняли с себя ответственность.
но ничто не мешает написать эту часть самому, как и описано в статье ("НуВызватьПаузу")
9. n_mezentsev 56 11.09.24 11:57 Сейчас в теме
(8) Да у меня тоже возникло такое ощущение, не говоря уж о любимом Борисом Георгиевичем сочетанием Look&Feel, в который пауза, которая вешает весь интерфейс, наверное, плохо вписывается (ну, это Feel, конечно) - обработчики ожидания ведь появились.
А вообще, если мне не изменяет память, в фоновых заданиях сеанс запускается в режиме однозадачности (если можно так выразиться), поэтому пауза там доступна и - без фонового пауза никак не видится, а фоновое ради паузы выглядит сомнительно, если их много, конечно (метод ухода от этого описан в P.S. к статье, гляньте, если интересно)
3. bayselonarrend 2027 08.09.24 22:25 Сейчас в теме
Я привык вот такой пользоваться, вроде нормально работает


Процедура Пауза(Знач Секунды) Экспорт

    Соединение = Новый HTTPСоединение("1C.ru", 11111, , , , Секунды);
    Попытка
        Соединение.Получить(Новый HTTPЗапрос(""));
    Исключение
        Возврат;
    КонецПопытки;

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

Показать
frkbvfnjh; n_mezentsev; +2 Ответить
4. SerVer1C 792 09.09.24 09:44 Сейчас в теме
(3) Может вылететь при резолвинге имени.
10. frkbvfnjh 803 17.09.24 14:08 Сейчас в теме
(3)
Соединение = Новый HTTPСоединение("127.0.0.0", , , , , КоличествоСекунд);
		Попытка	Соединение.Получить(Новый HTTPЗапрос()); Исключение КонецПопытки;
5. SerVer1C 792 09.09.24 09:45 Сейчас в теме
Пауза через фоновые задания возможна от 5+ секунд и она очень не точная.
7. n_mezentsev 56 11.09.24 11:03 Сейчас в теме
Я бы не сказал, что она неточная, по крайней мере на небольших отрезках точность в 20 миллисекунд - отлично, но это, конечно, по методу ОжидатьЗавершенияВыполнения(Таймаут). А вот если таймаут не указывать, то точность снизится на порядок, но это тоже вроде немного
Оставьте свое сообщение