Хочу предложить еще одно решение, основанное на механизме управляемых блокировок платформы 1С.
Итак, приступим:
Для реализации будем использовать:
1. Механизм управляемых блокировок
2. Механизм фоновых заданий и их метод: "ОжидатьЗавершенияВыполнения"
Смысл данной реализации схематично можно представить следующим образом:
Теперь к реализации:
1. Решение состоит из общего модуля и регистра сведений. (+ тестовая обработка для демонстрации работы паузы)
2. Регистр сведений необходим для реализации блокировки, по определенному условию. В нашем случае, это "НомерСеанса" (Число). Так как в одну единицу времени у информационной базы не может быть два сеанса с одинаковым номером, что позволяет нам утверждать, что пересечений по блокировкам не будет. Другими словами — одна пауза не помешает другому сеансу установить свою паузу.
3. Процедуры и функции
Процедура ВыполнитьПаузу_вСекундах(ВремяПуаузыСек,Причина="")
Параметры:
ВремяПаузыСек — Число — Количество секунд, на которое необходимо установить паузу
Причина — Строка — произвольный текст, будут установлен в запись регистра сведений
Текст с пояснениями:
Процедура ВыполнитьПаузу_ВСекундах(ВремяПаузыСек,Причина="") Экспорт
пНомерСеанса = НомерСеансаИнформационнойБазы(); // Номер текущего сеанса
пИмяИнициатора = ИмяПользователя(); // имя пользователя
// Создаем запись о паузе в регистре сведений
МЗ = РегистрыСведений.apПауза.СоздатьМенеджерЗаписи();
МЗ.НомерСеанса = пНомерСеанса;
МЗ.ДатаНачалаПаузы = ТекущаяУниверсальнаяДата();
МЗ.ДатаОкончанияПаузы = МЗ.ДатаНачалаПаузы + ВремяПаузыСек + 3; // Необходимо значение для сборщика зависших пауз (+3 секунды на всякий случай "оверхеда")
МЗ.Причина = Причина;
МЗ.ИмяИнициатора = пИмяИнициатора;
МЗ.Записать();
НачатьТранзакцию(); // Вне транзакций блокировка невозможна
Блокировка = новый БлокировкаДанных;
Элементблокировки = Блокировка.Добавить("Регистрсведений.apПауза");
Элементблокировки.УстановитьЗначение("НомерСеанса",пНомерСеанса);
Элементблокировки.Режим=РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать(); // Установили блокировку (ни один другой сеанс не может прочесть это значение)
МассивПараметров = Новый Массив;
МассивПараметров.Добавить(пНомерСеанса);
МассивПараметров.Добавить(МЗ.ДатаОкончанияПаузы + ВремяПаузыСек);
//Запускаем фоновое задание, передаем параметр текущего номера сеанса
ФЗ = ФоновыеЗадания.Выполнить("apПауза.сис_Пауза",МассивПараметров,,"пауза от " + пИмяИнициатора + " (" + пНомерСеанса + ")");
Если РежимСовместимости_8_13_ИВыше() Тогда // у платформы с 8.3.13 "ОжидатьЗавершенияВыполнения", более ранняя "ОжидатьЗавершения"
Попытка
ФЗ = ФЗ.ОжидатьЗавершенияВыполнения(ВремяПаузыСек); // Время ожидания равно времени паузы
Исключение;
КонецПопытки;
Иначе
Попытка
ФЗ.ОжидатьЗавершения(ВремяПаузыСек); // Время ожидания равно времени паузы
Исключение;
КонецПопытки;
КонецесЛИ;
ОтменитьТранзакцию(); // пауза закончена, снимем блокировку
МЗ.НомерСеанса = пНомерСеанса;
МЗ.Удалить(); // Удалим запись о паузе
КонецПроцедуры
Процедура сис_Пауза(НомерСеанса,ВремяОкончанияПаузы)
Параметры:
НомерСеанса — Число — Номер сеанса, который ставится на паузу
ВремяОкончанияПаузы— Дата в UTC- Когда должна закончится пауза (когда пауза больше времени ожидания на снятие блокировки)
Текст с пояснениями:
Процедура сис_Пауза(НомерСеанса,ВремяОкончанияПаузы) Экспорт
МЗ = РегистрыСведений.apПауза.СоздатьМенеджерЗаписи();
МЗ.НомерСеанса = НомерСеанса;
Пока ТекущаяУниверсальнаяДата() < ВремяОкончанияПаузы Цикл // Для пуаз больше 20 сек (Таймаута ожидания запроса по умолчанию)
Попытка;
МЗ.Прочитать(); // попытка чтения заблокированной запись - здесь и возникает ожидание паузы
Исключение;
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Процедура сборщика выполняется регламентным заданием и удаляет неактуальные записи, которые могли остаться в регистре сведений (не критично, будет работать и без регулярной чистки)
Функция получения режима совместимости и версии платформы, необходима для определения какой из методов ожидания завершения использовать у фонового задания
Процедура СборщикЗависшихПауз() Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| apПауза.НомерСеанса КАК НомерСеанса
|ИЗ
| РегистрСведений.apПауза КАК apПауза
|ГДЕ
| apПауза.ДатаОкончанияПаузы < &ТекущаяУниверсальнаядата";
запрос.УстановитьПараметр("ТекущаяУниверсальнаядата",ТекущаяУниверсальнаяДата());
Выборка = запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
МЗ = РегистрыСведений.Пауза.СоздатьМенеджерЗаписи();
МЗ.НомерСеанса = Выборка.НомерСеанса;
МЗ.Удалить();
КонецЦикла;
конецПроцедуры
Функция РежимСовместимости_8_13_ИВыше() Экспорт
ТекРежим = Метаданные.РежимСовместимости;
Если ТекРежим = Метаданные.СвойстваОбъектов.РежимСовместимости.НеИспользовать тогда
пСис = Новый СистемнаяИнформация;
пВерсия = пСис.ВерсияПриложения;
пВерсия = СтрЗаменить(пВерсия,".",Символы.ПС);
пПерваяВерсия = Число(СтрПолучитьСтроку(пВерсия,1));
пВтораяВерсия = Число(СтрПолучитьСтроку(пВерсия,2));
пТретьяВерсия = Число(СтрПолучитьСтроку(пВерсия,3));
Иначе
пВерсия = СтрЗаменить(СтрЗаменить(Строка(ТекРежим),"Версия",""),"_",Символы.ПС);
пПерваяВерсия = Число(СтрПолучитьСтроку(пВерсия,1));
пВтораяВерсия = Число(СтрПолучитьСтроку(пВерсия,2));
пТретьяВерсия = Число(СтрПолучитьСтроку(пВерсия,3));
КонецЕсли;
Если пТретьяВерсия <= 12 И пВтораяВерсия <= 3 И пПерваяВерсия = 8 Тогда
Возврат Ложь;
Иначе
Возврат Истина;
КонецЕсли;
КонецФункции
Тест паузы, как видно, в реальности пауза устанавливается на больше, чем указано в значении, примерно на 0.03 секунды.
Эти 30 мил.сек. как раз затраты на реализацию и обслуживание выполнения паузы.
Текст модуля формы обработки Теста паузы
&НаСервере
Процедура ВстатьНаПаузуНаСервере()
пТекДата = ТекущаяДата();
мНачало = ТекущаяУниверсальнаяДатаВМиллисекундах();
apПауза.ВыполнитьПаузу_ВСекундах(ПаузавСекундах,"Тестовая пауза");
Результаты.ДобавитьСтроку(Формат(пТекДата,"ДФ='dd.MM.yy HH:mm:ss'") + " пауза на " + Строка(ПаузавСекундах) +" сек. выполнена за " + Формат(ТекущаяУниверсальнаяДатаВМиллисекундах()-мНачало,"ЧГ=0") + " мил.сек.");
КонецПроцедуры
&НаКлиенте
Процедура ВстатьНаПаузу(Команда)
ВстатьНаПаузуНаСервере();
КонецПроцедуры
Теперь в любом месте конфигурации можно написать "apПауза.ВыполнитьПаузу_ВСекундах(5);"
И система встанет в ожидание на 5 секунд, при этом будет работать на любой платформе, без использования внешних компонент и большого потребления ресурсов сервера.
На картинке видно ожидание на управляемой блокировке, что фоновое задание, ждет, когда Тонкий клиент "отпустит" ресурс.
… Протестировано на нагрузке в 50 и более сеансов.
тестировалось на платформах 8.3.12, 8.3.15 без режима совместимости и в режиме совместимости 8.3.12
Для примера приведу рабочий кейс использования такой паузы:
Дано: система расчета показателей Apdex в режиме условно реального времени. По алгоритму для расчета индекса APDEX, необходимо знать время выполнения последних 100 таких же ключевых операций. Одна и та же ключевая операция может быть выполнена одновременно несколькими пользователями.
Решение:
1. Есть API, которая регистрирует выполнение ключевой операции в регистре сведений,
2. Регламентное задание, с периодичностью 1 раз в 60 сек стартует фоновые задания с анализом по каждому виду ключевых операции
3. Анализ по ключевой операции заключается в следующем:
Получение последних 100 записей из регистра сведений по данной ключевой операции
Создание таблицы значений с этими операциями
замещение новыми операциями, если есть (получение новых операций)
Подсчет итога по таблице
Установка паузы в 3 секунды и далее получение новых операций, без запроса к старым
Если нового замера по ключевой операции не поступало в течении 30 минут, то фоновое задание завершается
Такая реализация позволила убрать постоянный запрос к регистру с историей операций (более 10 млн. записей)
…
В файлах можно скачать расширение или конфигурацию, в которой реализован описанный выше механизм паузы.
…
А зачем вам нужна пауза в 1С?
Как-то очень уж сложно.
такой вариант
Я использовал вот
Без регистров, сборщиков пауз и т.д.
Показать
(1) единсвенный минус бсп. В самописке надо этот модуль перенести.
(2) создай свой модуль, добавь в него единственную процедуру из поста выше.
Это все что требуется.
Зачем БСП?
(3) ну не модуль, а процедуру.
(1) Возможно ли это сделать асинхронно с вызовом процедуры текущего сеанса по окончании таймаута?
Пауза в 3 секунды:
(6)
Эта функция использует Com объект, будет работать, но не везде (Например в linux не «взлетит»)
(5)
Не совсем понял вопроса. В описанном случае, после ожидания паузы, процедура дальше начнет выполнять следующую строчку кода
(1)
Коммент удалил, что б не вводить людей в заблуждение.
….
Все верно, можно и так!
Кстати, хороший вопрос. Кому и зачем какие паузы нужны? Мне нужны были только в пределах полусекунды, при работе с торговым оборудованием. Обходился бесконечным циклом. А три секунды и выше зачем могут понадобиться?
(9) Ты не понял. Это и есть текст этой процедуры 🙂 Ее необходимо разместить в общем модуле.
Хитрость в ожидании фоновым заданием завершения выполнения самого себя.
(10)
Такая пауза нужна, когда необходимо «подождать» событие, при этом «не потерять» значения собранных переменных. В описанном случае, что б заново не вытаскивать значения из БД.
(11)
Хитрость в ожидани
Я понял. Но пока идет ожидание. фоновое задание молотит бесконечный цикл. вот этим кодом:
(12) Подождать какое событие?
(13) И где ты в (1) увидел такой код? Ты таки не понял.
В (1) как раз та самая Пауза() которую надо разместить в «ОбщегоНазначенияСервер»
Там же прямо в комментарии написано: «Тут указываем имя общего модуля, в котором лежит данная процедура»
(14)
Когда «прилетит» новый замер по api, например
(15)
Разобрался. Ты прав.
Мой коммент (9) — чушь. Там клиентская пауза бестолковая.
Единственное, что заметил, чуть утекает память у rphost метров на 5 для каждой паузы.
Проц не грузит.
Решение элегантно)
(16) Когда надо с какой-то периодичностью получать данные, но при этом накапливать в памяти порции? Ну, такое… Достаточно узкий случай, как по мне.
(17) Странно. Т.е. ты говоришь о том, что после двухсот пауз будет «съеден» лишний гиг?
(18)
Глубоко не тестировал, накинул обработку, посмотрел состояние rphost. увидел увеличение памяти, после выполнения память освободилась.
(4) Я открою тайну, что любая самописка должна строиться на базе избранных подсистем БСП. Т.е. по инструкции отмечаете галками те подсистемы, что нужны, и переносите их в чистую конфу.
Иначе, если вы пишете с нуля, это изобретение целого велосипедного парка.
(10) у меня был такой случай:
есть сторонний сервис, который работает по схеме, что по первому запросу, он тебе возвращает ответ, что твой запрос взят в разработку.
а потом ты к нему обращаешься и запрашиваешь результат выполнения своего запроса.
вот тогда ты делаешь первый запрос.
потом ставишь паузу.
потом обращаешься повторно, чтобы получить результат обработки первого запроса
если пришел ответ, что запрос еще обрабатывается, то ставишь паузу и еще раз обращаешься.
(21) Мда, рецепт из категории «вредных советов».
Сервис потормозит дольше обычного — и что, потеряем данные? Ну или, в лучшем случае, затормозим обработку других тикетов.
(20) если конфа узкоспециализированная и БСП там не нужен, то зачем его тащить? Максимум 1-5 функций нужны.
(22)
Реализуется программно. Все что не получили ответа в течении n-времени, заново инициируем подключение и проверку.
(24) Нафиг, я в такие игры не играю. Пишу в регистр тикеты/статусы, потом обрабатываю регистр другим потоком.
В итоге, имеем хорошо разделенную логику, имеем хорошую масштабируемость.
(25) вы изменяете ранее записанные строки в регистре сведений (статус, время выполнения)? Или удаляете после обработки?
(26) На последнем решении — было сразу и то, и другое. Менял статусы — это сразу.
И работало задание очистки, которое совсем старую историю вычищало.
Но это уже совсем другая история;) То бишь, оффтоп.
(23) В узкоспециализированной конфе наверняка есть пользователи ИБ, есть отчёты и прочее другое, что есть среди подсистем БСП. Не вижу смысла с нуля писать то же самое и более низкого качества.
(22) задача понята неправильно — вывод сделан неверный.
Сервис не тормозит, а обрабатывает запросы асинхронно.
Сначала он твой запрос ставит в очередь на обработку, а потом ты к нему ходишь и запрашиваешь результат обработанного запроса.
Сервис государственный, если что
И в чем вредность, давай-ка разъясни
(28) ничего подобного там нет, т.к. не нужно.
(29) Вот сервис работает асинхронно. Пусть он даже «государственный, если что».
Ты-то почему тогда синхронно с ним работаешь?
Вред я уже описывал выше.
Вред один — это синхронность. Пока ты не прочитал один тикет — к другому не перейдешь. Ведь, поди что, в цикле с доп.паузами будешь пытаться прочитать. А на сервисе какая-нить фигня произойдет и один тикет будешь бесконечное время пытаться прочитать.
А вред другой — если будешь просто через паузу однократно прочитывать, без дальнейших попыток. Тогда просто за отведенное (тобой) время асинхронный сервис не подготовит данные. А твой поток пойдет дальше, не прочитав данные.
Вообще, твое решение с паузами все же имеет место. Но только в одном случае — если этого ждет пользователь, вот прямо здесь и сейчас.
Для пакетной работы — лучше сразу писать по-нормальному.
(8) (11) В приведенном коде ожидание происходит в текущем сеансе, а не в сеансе фонового задания. Потому ожидание вешает систему на время паузы. Если делать асинхронно, ожидание следует делать в сеансе фонового задания и тогда код должен быть таким:
Показать
Но в таком случае мы не сможем передать управление на сервер текущего сеанса после ожидания. Проще говоря, интересно бы видеть решение типа ПодключитьОбработчикОжидания для сервера.
(31) Ты так говоришь, как будто синхронность это что-то плохое. Асинхронность не бесплатна. Не стоит ее тащить туда, где можно без нее обойтись.
Нафига нам прорва масштабируемости, если мы точно знаем, что она не потребуется? Ну вот нафига? Очередь, воркеры, промежуточные этапы, машина состояний? Backpressure? Ради несколько раз в день дернуть «ручку»?
(32)
Эта задача и решалась. Пауза в текущем потоке, но которая не грузит проц. Стандартная пауза.
(32)я понял мысль. К сожалению, пока такой реализации нет, так как с сервера вызвать клиент не получится(
(28)Подтверждаю. Если писать к примеру конфу для учета продаж сетевиков или там учет для автомойки, БСП там на фиг не нужен.
Отчет на СКД 5 минут делается, всего их пара нужна. К чему слона тащить в багажник?
(37) Отлично, наконец-то есть примеры задач. Итак, если в конфе хоть один отчёт есть — ВариантыОтчетов. Они позволят управлять пользовательскими вариантами и шарить им их для других пользователей. Опять же, авторизация и разделение прав — УправлениеДоступом. Контроль заполнения документов, получение реквизита объекта на клиенте — БазоваяФункциональность. Выгружать данные в другие системы — ОбменДанными. Печатные формы документов — ПодключаемыеКоманды, Печать. Что-то переделали, надо массово изменить объекты — ГрупповоеИзменениеОбъектов. Какие движения делает документ — ОтчетОДвиженияхДокумента. Структура подчинённости — СтруктураПодчиненности. Накосячили при загрузке данных, задвоили элементы справочника — ПоискИУдалениеДублей. Отправить отчёт боссу на почту — РаботаСПочтовымиСообщениями.
Вы врядли сможете привести пример конфы, где не нужна БСП. Скорее всего, вы просто плохо её знаете, поэтому не понимаете, как она облегчает разработку и поддержку. И разработка с ней — это, конечно, быстрее, чем с нуля колхозить.
.
(37) Плохой пример. Для любой учетной конфы уже имеет смысл ставить ряд подсистем БСП. Тут я с (38) полностью согласен.
С (23) я готов согласиться только в случае чего-то слишком уж специализированного. Типа микросервиса на 1С который выполняет очень узкую сервисную задачу.
(37) любую конфу, если она не одноразовая, потребуется дорабатывать.
Сегодня у тебя пара отчетов, через год — уже пара десятков.
Чем больше ты написал кода, тем сложнее будет подключить БСП и тем больше объектов будут написаны на велосипедных схемах.
И после того, как ты передал конфу заказчику и он в ней начал работать — то уже не будешь подключать, потому что обосновать трудозатраты на подключение БСП на поздних этапах уже не сможешь.
Но подобный подход свойственен для новичков, а также для тех, кто не дружит с БСП.
(33) Для любого количества нужно делать без тормозов в интерфейсе. Собственно, как формирование отчётов в БСП — пока крутится кругляш, ты продолжаешь работать, а как сформировался отчёт — вывелся на экран в фоне. Когда будет время, тогда и глянешь на него.
Похоже, проблема только в незнании, насколько этот процесс прост. Кстати, формирование первичного запроса, его отправка и старт фонового задания — это выполняется в один поток, т.ч. «чтобы та процедура по обработке ответа выполнилась не раньше, чем закончит выполняться процедура по отправке тикета» не актуально.
(34) Ты вообще про 1С или жаву? В 1С достаточно запустить фоновое задание. Всё.
https://its.1c.ru/db/metod8dev#content:1528:hdoc
https://its.1c.ru/db/edtdoc#content:10047:1
https://its.1c.ru/db/v8315doc#bookmark:dev:TI000002036
Нет тормозов в интерфейсе, пользователь продолжает работать, а не ждёт, пока «программа отвиснет».
Рекомендую почитать «стандарты и методику разработки».
(43) Я про что угодно. А здесь речь вообще не про интерфейс.
(44) Тогда почитай ссылки, там подробнее описаны несколько строк кода, которые тебе понадобятся вместо всей этой мишуры.
(45) Ты просто немного не понял, о чем речь. Проехали.
(46) Я спросил жену (QA lead). 🙂 Все эти термины тут не уместны.
(47) Backpressure в контексте обсуждения было лишним 🙂 Остальное в тему.
Речь про задачу (21). С чего ты вообще взял, что инициатор события пользователь/клиент?
Нафига тут асинхронность, если можно спокойно это обработать в одном потоке?
ImHunter ведь дальше начал про регистры сведений (очереди) и обработку этой очереди отдельным потоком (воркером).
(42) тут же никто тебя не заставляет крутить бесконечно.
В таких случаях можно использовать другую схему:
Ты отправляешь тикет.
Один-два-три-десять раз его проверяешь — в зависимости от конкретной задачи.
Если ответ не получил — то отваливаешься.
Значит ответ на запрос придет через асинхронный поток.
Такой подход позволит тебе в некоторых частных случаях обрабатывать запрос значительно быстрее.
Если у тебя, например, стоит два асинхронных потока, из которых один отправляет тикеты, а второй загружает по ним ответы, то тебе придется дольше ждать.
А если еще в общем потоке много тикетов висят, то тебе нужно подождать пока вся очередь прогрузится.
А при синхронном варианте ты можешь быстро обработать нужный тебе тикет.
(49) Даже если говорить не про обработку очереди, а про обработку тычков пользователя, то асинхронность тоже без особых наворотов делается.
ПодключитьОбработчикОжидания(…) + ОписаниеОповещения([для хранения каких-то переменных и задания метода обработки]) + ВыполнитьОбработкуОповещения(…) — ну и вот, этим получаем асинхронное поведение.
(51) Сабж изначально про классическую паузу в текущем потоке. Иногда это самый адекватный вариант решения проблемы. Вообще не понимаю, почему вдруг про пользовательский интерфейс начали говорить. Ясен пень, пользовательский интерфейс должен быть отзывчив по максимуму.
Меня просто покоробила подача, что мол «асинхронность — наше все и всегда». Нет, не все и не всегда. Вечно народ из крайности в крайности кидает.
В частности для задачи (21), если по регламенту опрашивается какой-то сервис с асинхронным протоколом и коротким предсказуемым временем отклика, то гораздо проще использовать паузу, чем городить очередь. Если нет особых требований по пропускной способности и масштабируемости ессно.
А если и после паузы не срослось — то можно кидать эксепшн, настроив для регламента количество повторов при ошибках.
(52) Синхронность в виде Пауз не приветствуется разработчиками платформы (я бы сказал, категорически не приветствуется). Это мы видим и по отсутствию некой штатной процедуры Пауза(…), и по развитию асинхронных методов.
Поэтому крайностей тут и нет. Есть либо использование методологически правильных решений, либо разные самопальные реализации Пауз.
(53) ну так ты приведи ссылку, где сказано, что не приветствуется.
Ты же не разработчик платформы, чтобы так заявлять.
Это просто твои предположения и не более того.
(54) Я ведь привел свои соображения. Или ты ожидаешь, что в ИТС будет написано — не используйте решениеПауза на платформе 1С, очень нативная и очень бережная к ресурсам ?;)
(53) Дело не только в синхронности/асинхронности. Код превращается в дикую слабопонятную размазню при реализации асинхронности «методологически верно». Вот это больше всего парит, а не отзывчивость интерфейса и прочее.
(56) Да, на самом деле — дело не только в синхронности/асинхронности. Еще дело в (не)умении написать методологически верно.
(53) Ссылки на «авторитетов» без аргументации даже при наличии официальной позиции по этому поводу — слабый довод. А свои аргументы я привел.
(57) Писать асинхронно я умею. И даже других учил 🙂 И было бы хорошо, чтобы разработчики платформы настолько категорически приветствовали асинхронность, что реализовали побольше «сахара» в языке на эту тему (как в других языках/фреймворках).
(57) На самом деле дело еще в умении применять критическое мышление, в том числе и к рекомендациям 1С а не воспринимать их как истину в последней инстанции. Еще полезно высовывать нос за рамки конфигуратора для расширения кругозора.
(59) Высовывание носа за рамки конфигуратора — это явно не про эту тему. Она лишь в рамках конфигуратора и осуществлена.
(60)Как раз про эту. Чтоб понять что все может быть проще и лучше, но не в 1С, к сожалению. То что приходится вытворять, в том числе и в этой теме — дичь полная, но выбора нет. Хотя в силах разработчиков платформы его предоставить.
(41) А главное, что это «больше кода» написал не ты, а некий автор самописки и два-три его последователя, а твоих предшественника; и ты смотришь на ЭТО и думаешь: «И теперь мне ещё всё на БСП переводить?» И не то чтобы БСП тут не нужна, но… неподъёмно как-то…