Как показывать прогресс и лог на клиенте во время выполнения длительной серверной процедуры 1С
Разработка - Работа с интерфейсом
Файлы
ВНИМАНИЕ: Файлы из Базы знаний - это исходный код разработки. Это примеры решения задач, шаблоны, заготовки, "строительные материалы" для учетной системы. Файлы ориентированы на специалистов 1С, которые могут разобраться в коде и оптимизировать программу для запуска в базе данных. Гарантии работоспособности нет. Возврата нет. Технической поддержки нет.
Подписка PRO — скачивайте любые файлы со скидкой до 85% из Базы знаний
Оформите подписку на компанию для решения рабочих задач
Оформить подписку и скачать решение со скидкойВы можете заказать платную доработку или адаптацию этой разработки под вашу конфигурацию на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.
Серверная процедура работает несколько секунд или минут. Пользователь нажал кнопку и хочет видеть, что уже сделано, сколько осталось и не возникла ли ошибка. В статье разберём, как во время этой работы дописывать строки в лог формы и обновлять процент выполнения.
Для этого есть два подхода. Если работу можно разделить, клиент вызывает сервер короткими порциями и показывает результат каждой. Если сервер должен работать непрерывно, запускаем отдельное фоновое задание, а форма получает от него сообщения или периодически читает сохранённый прогресс.
У каждого способа есть отдельный пример: его можно запустить, посмотреть результат в форме и разобрать основную логику.
На главной форме можно выбрать способ вывода прогресса и открыть его пример.
Какой способ попробовать
- Работу можно разбить на части — №1. Выполняем одну часть, показываем результат и запускаем следующую. Пока идёт один серверный вызов, клиент ждёт; форма отвечает между вызовами.
- Работа должна идти отдельно от формы — №2–6. Сервер выполняет фоновое задание, а клиент продолжает обрабатывать свои команды. Примеры отличаются тем, как форма узнаёт о ходе работы.
- Хотите получать прогресс средствами библиотеки стандартных подсистем (БСП) — №3. В №3 форма использует обработчик прогресса БСП. Её запускатель используется и в остальных фоновых примерах.
- Нужен отдельный канал через WebSocket — №6. Для этого примера понадобится сервер-посредник. В статье показана передача сообщений через него; исходники есть в архиве.
Как запустить примеры
Откройте файл ProgressLogExamples_1_0.epf в УНФ через «Файл → Открыть». Для фоновых примеров нажмите «Подготовить файл для фоновой работы» и выберите этот же EPF. Этого достаточно один раз для всех примеров в открытой обработке. Затем выберите способ и нажмите «Начать».
В веб-клиенте БСП нужен сам файл внешней обработки, чтобы выполнить его функцию на сервере. Подготовка передаёт файл в текущий сеанс; устанавливать расширение или добавлять свои общие модули не требуется. Порционный способ №1 можно открыть без подготовки.
Пока работа продолжается, следите за процентом и новыми строками лога. Введите текст в поле заметки и нажмите «Проверить отклик»: в логе появится ответ клиентской команды. Кнопка «Остановить» позволяет запросить отмену.
Во всех фоновых примерах запуск выполняют штатные модули ДлительныеОперации УНФ/БСП. В модуле объекта EPF находится экспортная функция ВыполнитьРаботу; в неё передаются параметры выбранного способа и данные для отправки прогресса. Примеры отличаются получением событий, а запуск у них общий.
У каждого из шести способов отдельная форма; работа и отправка сообщений находятся в модуле объекта внешней обработки, в отдельных областях. В каждой форме отдельно выделены области СозданиеИнтерфейса, АлгоритмРаботы и СлужебныеПроцедуры. Изучение рабочего кода можно начать с области АлгоритмРаботы. Архив содержит готовую EPF, полные исходники обработки и WebSocket-посредника.
Что общего у способов с фоновым заданием
У всех примеров одна последовательность: запустить работу отдельно, сообщать о выполненных этапах, принять сообщения на клиенте и обновить поля формы. Примеры отличаются способом передачи этих сообщений.
Дальше код показан фрагментами по этим действиям. Каждый блок относится к конкретному месту алгоритма. Создание элементов формы и остальные служебные процедуры находятся в исходниках обработки.
Когда сообщать о ходе работы
Внутри длительной работы после очередного выполненного этапа вызывается Передать. В этот момент уже известны номер этапа и общее число этапов.
Передать(Контекст, История, Цел(НомерЭтапа * 100 / Контекст.Этапов),
"Этап " + Формат(НомерЭтапа,"ЧГ=0") + " из " + Формат(Контекст.Этапов,"ЧГ=0"));
Процент вычисляется как доля выполненных этапов. Вместо демонстрационного расчёта здесь может находиться обработка очередной порции данных. Важно вызывать отправку по ходу работы, чтобы форма получала обновления до её завершения.
Что передаём: сообщение о выполненном этапе
После важного шага сервер собирает данные для формы. Это короткий фрагмент процедуры Передать.
Пакет = Новый Структура("Ключ,Номер,Мс,Процент,Текст", Контекст.Ключ, История.Количество()+1,
ТекущаяУниверсальнаяДатаВМиллисекундах(), Процент, ТекстСобытия);
Текст и процент нужны для экрана. Ключ отличает текущий запуск от предыдущего, номер позволяет пропустить повторное сообщение. История используется теми способами, которые передают накопленный список строк.
Передать БСП файл внешней обработки
В главном окне файл EPF один раз помещается во временное хранилище. Его адрес передаётся всем фоновым примерам. Перед запуском форма получает двоичные данные по этому адресу и помещает их в описание внешней обработки.
ОписаниеВнешнегоОбъекта = ДлительныеОперации.НовоеОписаниеВнешнегоОтчетаОбработки();
ОписаниеВнешнегоОбъекта.Данные = ПолучитьИзВременногоХранилища(ЭтаФорма.АдресФайла);
БСП получает саму внешнюю обработку, в модуле объекта которой находится работа. Устанавливать свои общие модули в конфигурацию не требуется.
Попросить БСП запустить работу в фоне
Описание файла передаётся в параметры длительной операции.
ПараметрыФона = ДлительныеОперации.ПараметрыВыполненияФункции(ЭтаФорма.УникальныйИдентификатор);
ПараметрыФона.ВнешнийОтчетОбработка = ОписаниеВнешнегоОбъекта;
ПараметрыФона.ЗапуститьВФоне = Истина;
ПараметрыФона.ОжидатьЗавершение = 0;
ПараметрыФона.НаименованиеФоновогоЗадания = "Проба 11: "+ЭтаФорма.Канал;
ЗапуститьВФоне включает отдельное выполнение, ОжидатьЗавершение = 0 возвращает управление без ожидания всей работы. Эта подготовка используется во всех фоновых примерах.
Вызвать функцию модуля объекта EPF
Контекст содержит выбранный способ доставки, ключ запуска, параметры работы и номер клиентского сеанса. Вызов БСП указывает экспортную функцию модуля объекта внешней обработки.
Возврат ДлительныеОперации.ВыполнитьФункцию(ПараметрыФона,"ВнешняяОбработка.ProgressLogExamples.МодульОбъекта.ВыполнитьРаботу",Контекст);
В модуле объекта функция ВыполнитьРаботу разрешена для вызова через БСП обработчиком ПриОпределенииМетодовРазрешенныхДляВызоваКакПроизвольныйКод. Возвращённое описание операции содержит идентификатор задания и адрес конечного результата.
Что делает клиент с полученным сообщением
Все способы в итоге приводят к одному действию на клиенте: обновить процент и дописать строку в поле лога. Это фрагмент ПринятьСобытие.
ЭтаФорма.Процент = Макс(0,Событие.Процент);
ДобавитьЛог("Событие №"+Формат(Событие.Номер,"ЧГ=0")+": "+Событие.Текст
+"; отправка="+Формат(Событие.Мс,"ЧГ=0")+"; прием="+Формат(МоментПриема,"ЧГ=0")
+"; состояние="+ЭтаФорма.СостояниеЗадания);
ДобавитьЛог дописывает текст к реквизиту Лог с переводом строки. В примере рядом с текстом видны время отправки, время приёма и состояние задания: по ним можно понять, когда строка появилась на экране.
Как дописывается строка в поле лога
ДобавитьЛог использует реквизит формы Лог. Само дописывание занимает одну строку.
ЭтаФорма.Лог=ЭтаФорма.Лог+?(ЭтаФорма.Лог="","",Символы.ПС)+Формат(ТекущаяДата(),"ДФ=HH:mm:ss")+" "+ТекстСобытия;
Если лог уже заполнен, перед новым текстом добавляется перевод строки. В поле остаются предыдущие сообщения, а рядом с новым записывается клиентское время. Этот код выполняется на клиенте после получения данных от сервера.
№1. Без фона — порционные вызовы. Форма обновляет прогресс между короткими серверными вызовами
Схема: клиент вызывает сервер → сервер выполняет одну порцию и возвращает результат → клиент обновляет экран и назначает следующую порцию. Так можно показать ход общей работы без фонового задания. Следующий вызов назначается через одноразовый обработчик ожидания. Чем дольше выполняется отдельная порция, тем дольше клиент ждёт её завершения.
Работа завершена: форма показывает выполненные шаги, сумму и накопленный лог.1. Запомнить, на каком шаге остановились
Сервер сохраняет количество шагов, размер порции и уже выполненную часть. Клиент хранит адрес этой записи и передаёт его следующему вызову.
СостояниеЗадания = Новый Структура("Всего,Порция,ШагОшибки,Выполнено,Сумма,НомерПорции", КоличествоШагов, Порция, ШагОшибки, 0, 0, 0);
АдресЗадания = ПоместитьВоВременноеХранилище(СостояниеЗадания, ИдентификаторФормы);
Для порционного способа достаточно временного хранилища: все порции запускает та же форма. При каждом вызове сервер достаёт состояние по сохранённому адресу.
2. Определить границы очередной порции
Порция начинается после последнего выполненного шага и заканчивается через заданное число шагов.
НачалоПорции = СостояниеЗадания.Выполнено + 1;
КонецПорции = Мин(СостояниеЗадания.Выполнено + СостояниеЗадания.Порция, СостояниеЗадания.Всего);
Мин не даёт выйти за общее число шагов. Внутри этих границ выполняется прикладная работа; в демонстрации сервер суммирует номера шагов.
3. Вернуть результат и сохранить продолжение
После обработки сервер собирает ответ: выполненные шаги, новые строки лога, признак завершения и текст ошибки.
Готово = СостояниеЗадания.Выполнено = СостояниеЗадания.Всего Или ТекстОшибки <> "";
ОтветПорции = СнимокРезультата(СостояниеЗадания, Готово, ТекстОшибки, СобытияПорции);
Если Готово Тогда
УдалитьИзВременногоХранилища(АдресЗадания);
Иначе
ПоместитьВоВременноеХранилище(СостояниеЗадания, АдресЗадания);
КонецЕсли;
Возврат ОтветПорции;
Если работа продолжается, состояние записывается по прежнему адресу. Если закончилась или возникла ошибка, запись удаляется. СнимокРезультата собирает эти значения в структуру ответа.
4. Показать ответ на клиенте
Обработчик ожидания вызывает сервер только за одной порцией, а затем передаёт ответ в ПринятьРезультат.
ОтветПорции = ШагНаСервере(ЭтаФорма.АдресСостояния);
ПринятьРезультат(ОтветПорции);
ПринятьРезультат обновляет число выполненных шагов и дописывает полученные строки в лог. Это происходит после возврата текущего вызова.
5. Назначить следующую порцию
Если в ответе Готово = Ложь, клиент снова подключает одноразовый обработчик ожидания.
ПодключитьОбработчикОжидания("ВыполнитьСледующуюПорцию", ЭтаФорма.ИнтервалОжидания, Истина);
Между вызовами форма успевает показать изменения и обработать команды пользователя. Пока выполняется сама порция, клиент ждёт. Поэтому размер порции определяет, насколько долго форма может не отвечать.
№2. Фон → уведомления клиенту. Фоновое задание отправляет уведомления, а форма дописывает лог
Схема: форма подписывается на уведомления → запускает фоновое задание → сервер отправляет сообщения, а форма дописывает лог. Сервер продолжает работу после возврата запускающего вызова. Для отправки используется УведомленияКлиента: в параметры передаются ключ подписки и номер клиентского сеанса. Обработчик ожидания отдельно проверяет, завершилось ли задание.
Задание ещё работает, а в форме уже появились строки этапов и ответ на кнопку «Проверить отклик».1. Подписать форму до запуска задания
В начале запуска форма создаёт новый КлючЗапуска. Затем подключает обработчик уведомлений с этим ключом и только после этого вызывает запуск фонового задания.
УведомленияКлиента.ПодключитьОбработчик(ЭтаФорма.КлючЗапуска,Новый ОписаниеОповещения("Уведомление",ЭтотОбъект));
Уведомление — имя экспортного клиентского обработчика. Подписка должна существовать к моменту первой серверной отправки.
2. Отправить сообщение в нужный клиентский сеанс
Контекст.Сеанс — номер клиентского сеанса, переданный при запуске. Контекст.Ключ совпадает с ключом подписки формы.
Адресаты=Новый Массив;
Адресаты.Добавить(Контекст.Сеанс);
УведомленияКлиента.ОтправитьУведомление(Контекст.Ключ,Пакет,Адресаты);
Платформа адресует сообщение указанному сеансу. Пакет содержит данные о выполненном этапе; сервер продолжает работу после отправки.
3. Передать уведомление в обработку формы
На клиенте срабатывает Уведомление. Он передаёт данные в ПринятьСобытие, пока запуск активен.
Процедура Уведомление(Событие,ДополнительныеПараметры) Экспорт
Если ЭтаФорма.Активна Тогда ПринятьСобытие(Событие); КонецЕсли;
КонецПроцедуры
Далее форма проверяет принадлежность сообщения текущему запуску и выводит его. Реквизиты формы меняет клиентский код.
4. Не вывести чужую или повторную строку
Перед обновлением процента и лога форма проверяет ключ и номер сообщения.
Если Событие.Ключ <> ЭтаФорма.КлючЗапуска Тогда Возврат; КонецЕсли;
Маркер = "|"+Формат(Событие.Номер,"ЧГ=0")+"|";
Если СтрНайти(ЭтаФорма.НомераСобытий,Маркер)>0 Тогда Возврат; КонецЕсли;
ЭтаФорма.НомераСобытий = ЭтаФорма.НомераСобытий+Формат(Событие.Номер,"ЧГ=0")+"|";
НомераСобытий хранит уже принятые номера. Если строка пришла повторно, обработчик выходит до изменения экрана. После этих проверок выполняется общий фрагмент вывода, показанный выше.
5. Снять подписку после завершения
Когда форма закончила работу с заданием, она отключает обработчик по ключу текущего запуска.
Если ЭтаФорма.КлючЗапуска<>"" Тогда УведомленияКлиента.ОтключитьОбработчик(ЭтаФорма.КлючЗапуска); КонецЕсли;
При новом запуске создаётся другой ключ и новая подписка. Отдельный короткий опрос проверяет состояние фонового задания и прекращается после завершения или отмены.
Почему одного Асинх недостаточно
При предварительной проверке обычного длительного серверного вызова уведомления обрабатывались только после его возврата. Клиентская обёртка с Асинх дала тот же результат. Поэтому в этом примере работа выполняется отдельным фоновым заданием: запускающий вызов быстро возвращается, а форма принимает сообщения, пока задание работает.
№3. БСП — запуск и прогресс. Форма получает прогресс средствами БСП
Схема: БСП запускает работу отдельно → сервер сообщает процент и выполненные этапы → БСП передаёт их форме. В примере ожидание настроено без блокирующего окна. ДлительныеОперации.ВыполнитьФункцию запускает работу, а ДлительныеОперацииКлиент.ОжидатьЗавершение подключает получение прогресса и итога. Чтобы не потерять строки между обновлениями, сервер передаёт накопленную историю. Последние строки могут прийти уже вместе с итогом.
БСП передаёт ход работы в форму: лог пополняется, поле заметки и клиентская кнопка доступны.1. Сообщить процент и выполненные этапы
После каждого этапа сервер вызывает СообщитьПрогресс. Вместе с процентом и текущим текстом он передаёт историю сообщений.
ДлительныеОперации.СообщитьПрогресс(Макс(0,Пакет.Процент),Пакет.Текст,
Новый Структура("История",История));
Если передавать только последнюю строку, форма может не увидеть этапы между двумя обновлениями. История позволяет восстановить их, а проверка номеров защищает от повторного вывода.
2. Получать обновления без блокирующего окна
На клиенте задаются обработчики прогресса и завершения. Окно ожидания отключено, чтобы использовать собственную форму с логом.
НастройкиОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
НастройкиОжидания.ВыводитьОкноОжидания = Ложь;
НастройкиОжидания.ВыводитьПрогрессВыполнения = Истина;
НастройкиОжидания.Интервал = 1;
НастройкиОжидания.ОповещениеОПрогрессеВыполнения = Новый ОписаниеОповещения("ПрогрессБСП",ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(Операция,Новый ОписаниеОповещения("ЗавершениеБСП",ЭтотОбъект),НастройкиОжидания);
БСП сама организует получение состояния. При обновлении она вызывает ПрогрессБСП, а при завершении — ЗавершениеБСП. Интервал задаётся в настройках; фактическая частота обновлений зависит от механизма БСП.
3. Дописать полученную историю в лог
Из обновления БСП берётся переданный сервером список сообщений. Каждая запись проходит через общую ПринятьСобытие.
Если Состояние.Прогресс <> Неопределено И Состояние.Прогресс.Свойство("ДополнительныеПараметры") Тогда
Для Каждого Событие Из Состояние.Прогресс.ДополнительныеПараметры.История Цикл ПринятьСобытие(Событие); КонецЦикла;
КонецЕсли;
Форма добавляет только ещё не показанные строки. Если последние сообщения пришли вместе с итогом, они появляются уже после завершения работы; это следует отличать от промежуточного вывода.
Механизм БСП описан в стандарте длительных операций. Для переноса этого примера в свою конфигурацию понадобятся соответствующие модули БСП.
№4. Опрос сообщений фонового задания. Форма периодически забирает сообщения работающего задания
Схема: сервер вызывает Сообщить → сообщения накапливаются у фонового задания → форма периодически забирает их и дописывает лог. Для чтения короткий серверный вызов находит задание и вызывает ПолучитьСообщенияПользователю(Истина). Значение Истина удаляет прочитанные сообщения из очереди, поэтому их должен забирать один обработчик. Этот пример также запускается через БСП УНФ. Её завершающая обёртка может забрать остаток сообщений в свою очередь, поэтому форма читает обе очереди.
Форма уже вывела сообщения о выполненных этапах, хотя фоновое задание ещё не закончено.1. Записать сообщение в очередь задания
Сервер использует обычное Сообщить внутри фонового задания. СтрокаПакета превращает данные этапа в текст с разделителями.
Сообщить(СтрокаПакета(Пакет));
В тексте передаются ключ запуска, номер, время, процент и сообщение. Эти сообщения относятся к выполняющемуся фоновому заданию.
2. Периодически забирать новые строки
После запуска задания форма сохраняет его идентификатор и назначает короткий опрос.
ПодключитьОбработчикОжидания("Опрос",0.3,Истина);
Опрос вызывает сервер, получает новые сообщения и выводит их через ПринятьСобытие. Пока работа продолжается, он назначает следующий опрос; после завершения или отмены прекращает его.
3. Забрать сообщения из двух очередей
Короткий серверный вызов находит задание по идентификатору. Пока работа идёт, сообщения доступны у задания. Обёртка БСП при завершении может забрать оставшиеся строки в свою очередь.
СообщенияЗадания=Новый Массив;
Для Каждого Сообщение Из Задание.ПолучитьСообщенияПользователю(Истина) Цикл СообщенияЗадания.Добавить(Сообщение); КонецЦикла;
// Обёртка БСП сама забирает остаток сообщений при завершении. Читаем и её очередь.
Для Каждого Сообщение Из ДлительныеОперации.СообщенияПользователю(Истина,Новый УникальныйИдентификатор(Идентификатор)) Цикл СообщенияЗадания.Добавить(Сообщение); КонецЦикла;
Поэтому сборщик читает и платформенную очередь задания, и очередь БСП. Параметр Истина удаляет полученные сообщения после чтения; второй независимый читатель может забрать их раньше формы.
4. Превратить текст сообщений в события формы
В обеих очередях строки содержат ключ запуска, номер, время, процент и текст. Сборщик разбирает эти поля.
Для Каждого Сообщение Из СообщенияЗадания Цикл
Части=СтрРазделить(Сообщение.Текст,"|");
Если Части.Количество()>=5 И Части[0]=Ключ Тогда
Ответ.События.Добавить(Новый Структура("Ключ,Номер,Мс,Процент,Текст",Ключ,Число(Части[1]),Число(Части[2]),Число(Части[3]),Части[4]));
КонецЕсли;
КонецЦикла;
Возврат Ответ;
Ответ.События возвращается клиенту. ПринятьСобытие пропускает повторы, обновляет процент и дописывает лог. Последние строки из очереди БСП не подменяются историей конечного результата.
№5. Опрос сохранённого состояния. Сервер сохраняет прогресс, а форма периодически читает его
Схема: сервер сохраняет список выполненных этапов → форма периодически читает его → новые строки появляются в логе. В примере для хранения используется ХранилищеОбщихНастроек. У каждого запуска свой ключ, чтобы результаты разных запусков не смешивались. После завершения форма удаляет свою запись. Для частых обновлений и больших логов способ хранения, предел объёма и очистку после обрыва нужно продумать отдельно.
Сервер продолжает работу, а форма уже прочитала и показала сохранённые этапы.1. Сохранить накопленный прогресс
После очередного этапа фоновое задание сохраняет историю под ключом текущего запуска.
ХранилищеОбщихНастроек.Сохранить("ProgressProbe11",Контекст.Ключ,
Новый Структура("История",История));
ХранилищеОбщихНастроек доступно серверным вызовам разных сеансов. Поэтому отдельное фоновое задание может записывать данные, а короткий вызов формы — читать их.
2. Прочитать историю при очередном опросе
Обработчик ожидания работает так же, как в №4. Разница в источнике: здесь сервер читает запись по ключу, а не очередь сообщений задания.
Снимок=ХранилищеОбщихНастроек.Загрузить("ProgressProbe11",Ключ);
Если Снимок<>Неопределено Тогда Ответ.События=Снимок.История; КонецЕсли;
Форма получает всю историю, но выводит только новые номера через ПринятьСобытие. Отсутствие записи в начале запуска означает, что сервер ещё не сохранил первый этап.
3. Удалить запись после завершения
Когда данные этого запуска больше не нужны, форма удаляет запись текущего пользователя.
Если Ключ<>"" Тогда ХранилищеОбщихНастроек.Удалить("ProgressProbe11",Ключ,ИмяПользователя()); КонецЕсли;
При аварийном обрыве обычная очистка может не выполниться. Для длительного использования нужно отдельно определить срок хранения и очистку оставшихся записей.
№6. WebSocket — нужен сервер-посредник. Сервер передаёт строки лога форме через посредника
Схема: форма и фоновое задание подключаются к серверу-посреднику → задание отправляет сообщение → посредник передаёт его форме. Сначала подключается форма, и только затем запускается работа: так она готова принять первые сообщения. Обе стороны используют штатный WebSocket-клиент 1С; сервер-посредник в этом примере — отдельная программа на Python. Короткий опрос следит за завершением задания, а строки лога приходят через WebSocket.
Форма получает строки через WebSocket во время работы задания; заметка и клиентская кнопка доступны.1. Сначала подключить форму
Форма открывает соединение по адресу посредника с ключом запуска. В ОбработчикиWS указаны имена процедур для открытия, сообщения, ошибки и закрытия.
ОбработчикиWS=Новый ОбработчикиWebSocketКлиентСоединения("WSОткрыт","WSСообщение","WSОшибка","WSЗакрыт",ЭтотОбъект);
WebSocketКлиентСоединения.ОткрытьСоединение(ЭтаФорма.КлючЗапуска,ЭтаФорма.АдресWS+ЭтаФорма.КлючЗапуска,ОбработчикиWS);
Только после подтверждения подключения можно запускать работу. Иначе сервер может отправить первые строки раньше, чем форма начнёт их принимать.
2. Запустить фон после подключения
Платформа вызывает WSОткрыт, когда клиентское соединение готово.
Если ЭтаФорма.Активна Тогда
ДобавитьЛог("WebSocket клиента подключён");
ПродолжитьЗапуск();
КонецЕсли;
ПродолжитьЗапуск вызывает серверный запуск задания и подключает проверку его состояния. Само задание выполняется отдельно по общей схеме запуска.
3. Открыть второе соединение на сервере 1С
Фоновый исполнитель подключается к тому же посреднику, добавляя тот же ключ запуска к адресу.
WebSocketКлиентСоединения.ОткрытьСоединение("server:"+Контекст.Ключ,Контекст.АдресWS+Контекст.Ключ);
У формы и задания разные соединения, но одинаковый адрес канала. Посредник связывает их по этому адресу.
4. Отправить строку после выполненного этапа
Сервер получает своё соединение и отправляет данные этапа, преобразованные в строку.
СоединениеWS=WebSocketКлиентСоединения.ПолучитьСоединение("server:"+Контекст.Ключ);
СоединениеWS.ОтправитьСообщение(СтрокаПакета(Пакет));
Это действие выполняется из фоновой работы. На стороне клиента оно приведёт к вызову WSСообщение после передачи через посредника.
5. Разобрать сообщение и обновить форму
Клиентский обработчик проверяет активность запуска, тип сообщения и совпадение ключа.
Если Не ЭтаФорма.Активна Или ТипЗнч(Сообщение)<>Тип("Строка") Тогда Возврат; КонецЕсли;
Части=СтрРазделить(Сообщение,"|");
Если Части.Количество()>=5 И Части[0]=ЭтаФорма.КлючЗапуска Тогда
ПринятьСобытие(Новый Структура("Ключ,Номер,Мс,Процент,Текст",Части[0],Число(Части[1]),Число(Части[2]),Число(Части[3]),Части[4]));
КонецЕсли;
ПринятьСобытие получает те же поля, что в остальных способах, проверяет номер, обновляет процент и дописывает лог. Проверка состояния задания выполняется отдельно от приёма этих строк.
Как запустить WebSocket-посредник
В общем ZIP с обработкой есть папка WebSocket: relay.py — сам посредник, requirements.txt — зависимость, README.md — пошаговая инструкция. Это отдельная программа для способа №6; запустите её перед нажатием «Начать» в форме.
На Linux из папки распакованного архива установите зависимость командой python3 -m pip install -r WebSocket/requirements.txt (в инструкции показан запуск в отдельном виртуальном окружении). Посредник принимает подключения на адресе, заданном параметром --host; без него посредник слушает только 127.0.0.1. Доступный серверу 1С и браузеру адрес задаётся также в поле формы: ws://имя-сервера:8897/probe/. Например: python3 WebSocket/relay.py --host 192.168.1.10 --port 8897, а в форме — ws://192.168.1.10:8897/probe/.
Форма и фоновое задание добавляют один и тот же ключ запуска к адресу подключения. Посредник пересылает текст только соединениям с одинаковым ключом. Историю он не хранит. Авторизацию, восстановление соединения и повторную доставку этот локальный пример не реализует. Для HTTPS-клиента понадобится защищённый посредник wss://; его я здесь не проверял.
Что делает посредник с полученным текстом
Внутри обработчика подключённого участника выбираются остальные соединения того же канала.
targets=[other for other in list(channels[path]) if other is not ws]
if targets:
await asyncio.gather(*(other.send(payload) for other in targets),return_exceptions=True)
Каждому выбранному соединению отправляется тот же текст. Код запуска сервера, проверки адреса и очистки соединений находится в исходниках посредника в архиве.
Документация 1С о штатном WebSocket-клиенте описывает создание соединений в клиенте и на сервере. В приведённой схеме 1С открывает соединения, а принимает их отдельный Python-посредник.
Как убедиться, что прогресс приходит во время работы
Нажмите «Начать» и дождитесь строки о выполненном этапе, пока задание ещё активно. Затем введите заметку и нажмите «Проверить отклик». Если в логе появилась строка с вашей заметкой до завершения задания, форма продолжает выполнять клиентские команды во время серверной работы.
Можно также задать ошибку на третьем этапе: в логе должны появиться её подробности. После завершения или отмены можно запустить пример заново. Эти сценарии проверены для №2, №3, №4, №5 и №6. В №1 форма получает результат между порциями; ошибка задаётся номером шага.
Поле с полным логом после завершения само по себе ещё не показывает, что прогресс приходил во время работы. Смотрите, появляются ли строки этапов, пока сервер занят. При предварительной проверке обычного вызова уведомления пришли только после возврата.
Готовые примеры и исходники
В архиве — готовая обработка, исходники каждого примера и WebSocket-посредник. Можно запустить нужный способ в отдельной форме и сопоставить его поведение с разобранными фрагментами.
На чём проверено: я проверял примеры на платформе 1С:Предприятие 8.5.1.1522 в веб-клиенте «Управление нашей фирмой», редакция 3.0, версия 3.0.14.143, под администратором. На 8.3, в тонком клиенте, под обычными правами и при промышленной нагрузке я не проверял. WebSocket проверял с локальным Python-посредником.
Проверено на следующих конфигурациях и релизах:
- Управление нашей фирмой, редакция 3.0, релизы 3.0.14.143
Вступайте в нашу телеграмм-группу Инфостарт
