Лучшая стратегия для сообщения о прогрессе в UI - как должен происходить обратный вызов?


11

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

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

Очевидно, что где-то должен быть обратный вызов, чтобы сделать эту работу. Я вообще реализовал это одним из двух способов:

  1. Передайте изменяемый объект в бэкэнд, и пусть бэкэнд внесет в него изменения в процессе работы. Объект уведомляет интерфейс, когда происходит изменение.

  2. Передайте функцию обратного вызова в форме void f(ProgressObject)или, ProgressObject -> unitкоторую вызывает серверная часть. В этом случае серверная часть создает ProgressObjectи она полностью пассивна. Я предполагаю, что он должен создавать новый объект каждый раз, когда хочет сообщить о прогрессе.

Каковы недостатки и преимущества этих методов? Есть ли согласованный лучший метод для использования? Существуют ли разные обстоятельства для их использования?

Существуют ли совершенно разные методы отчетности о прогрессе, которые я пропустил?


1
Что касается изменяемого и неизменяемого, то преимущества и недостатки такие же, как и везде. Что касается объекта прогресса, это может быть очень легко; это может быть так же просто, как одно число: процент.
Роберт Харви

@RobertHarvey Размер объекта прогресса обычно зависит от требований пользовательского интерфейса. Посмотрите на диалог копирования Windows, например. Я полагаю, это требует много информации.
GregRos

1
@RobertHarvey Это новость для меня. Что это такое?
ГрегРос

1
Я укушу Мы используем BackgroundWorkerэто RH упоминает. Завернут в пользовательский класс вместе с «формой прогресса» и т. Д. И простым механизмом сообщения об исключении - так как BackgroundWorkerпо замыслу выполняется в отдельном потоке. Если мы будем использовать его возможности способом, предложенным .Net, то это можно назвать идиоматическим. И в любом конкретном языковом / каркасном контексте «идиоматический» может быть лучшим.
Радар Боб

2
Я не вижу каких-либо существенных различий между вашими двумя методами. Объект, переданный от внешнего интерфейса к внутреннему, который предлагает методы, которые ведут к уведомлению внешнего интерфейса, на самом деле имеет функцию обратного вызова. И если ваш второй подход использует более или менее сложный объект параметров для передачи информации, или если он использует несколько простых значений, это не имеет значения с архитектурной точки зрения. В обоих подходах серверная часть активно информирует интерфейс, различия являются лишь незначительными деталями, поэтому здесь не описано никакой другой концепции.
Док Браун

Ответы:


8

Передайте изменяемый объект в бэкэнд, и пусть бэкэнд внесет в него изменения в процессе работы. Объект уведомляет интерфейс, когда происходит изменение.

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

Передайте функцию обратного вызова в форме void f (ProgressObject) или ProgressObject -> unit, которую вызывает серверная часть. В этом случае серверная часть создает объект ProgressObject, и он полностью пассивен. Я предполагаю, что он должен создавать новый объект каждый раз, когда хочет сообщить о прогрессе.

Я не вижу здесь такой разницы.

Существуют ли совершенно разные методы отчетности о прогрессе, которые я пропустил?

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


2

Это разница между нажимным и тяговым механизмом уведомления.

Изменяемый объект ( извлечение ) должен будет повторно опрашиваться пользовательским интерфейсом и синхронизироваться, если вы ожидаете, что фоновая задача будет выполнена в фоновом / рабочем потоке.

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

В целом push- уведомления предпочтительнее, потому что они выполняют работу только тогда, когда работа должна быть выполнена.


1
Я думаю, что вы говорите, в целом правильно. Тем не менее, для этого конкретного случая, индикатор выполнения, изменения могут произойти быстро. Если вы ожидаете, что «прогресс» может меняться много раз в секунду, имеет смысл использовать модель «pull», так как в противном случае вам придется беспокоиться о том, что пользовательский интерфейс получит слишком много уведомлений для обработки.
Gort the Robot

Отправка объекта прогресса может затенить механизм уведомлений, который вы используете, от внутреннего сервера, поскольку, возможно, объект прогресса выполняет обратные вызовы. На самом деле я никогда не использовал механизм вытягивания, насколько я помню, и я вроде как забыл об этом: P
GregRos

The mutable object (the pull) will need to be repeatably polled by the UI and synchronized if you expect the back-end task to be executed in a background/worker thread.- Нет, если изменяемый объект - это само диалоговое окно или рабочий интерфейс к нему. Конечно, это равнозначно обратному вызову.
Роберт Харви

1
А? ОП четко описывает две разные формы толкающего механизма, ни в одном из которых не требуется опрос.
Док Браун

0

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


0

Вы упоминаете свои «два пути», как если бы они были отдельными понятиями, но я хочу немного отступить от этого.

  1. Передайте изменяемый объект в бэкэнд, и пусть бэкэнд внесет в него изменения в процессе работы. Объект уведомляет интерфейс, когда происходит изменение.

Вы уже сказали, что хотите избежать тесной связи между пользовательским интерфейсом и логикой, поэтому я могу с уверенностью предположить, что этот «изменяемый объект», который вы передаете, на самом деле является реализацией определенного интерфейса, который определен в логическом модуле. Таким образом, это просто еще один способ передачи обратного вызова в процесс, который периодически вызывает информацию о прогрессе.

Что касается преимуществ и недостатков ...

Недостатком метода (1) является то, что класс, реализующий интерфейс, может сделать это только один раз. (Если вы хотите выполнять разные задания с разными вызовами, вам понадобится оператор switch или шаблон посетителя.) При использовании метода (2) один и тот же объект может использовать разный обратный вызов для каждого вызова кода бэкэнда без необходимости переключатель.

Преимущество метода (1) состоит в том, что гораздо проще иметь несколько методов в интерфейсе, чем иметь дело с множественными обратными вызовами метода (2) или одиночным обратным вызовом с оператором switch для нескольких контекстов.


-2

Методы, которые вы можете использовать, могут быть очень разными.

Я пытаюсь разобраться с другим сценарием

  • Просьба к БД
  • Скачать файл

Простой запрос на вход в db (имеется в виду ответ от db одним элементом) не требует выполнения отчета, но он всегда может запустить поток пользовательского интерфейса в отдельной задаче ex. async или backgroundworker, здесь вам просто нужен один обратный вызов для результата.

Но что, если вы запросите, чтобы увидеть все ваши ресурсы с 1 млн. Единиц? Выполнение этого запроса может занять несколько минут, поэтому в этом случае вам необходимо реализовать прогресс вашего порта в вашей бизнес-логике в элементе / элементах формы, затем вы можете обновить свой пользовательский интерфейс и, возможно, задать опцию отмены обратного вызова.

Тот же случай для загрузки файла. Здесь вы всегда можете реализовать свой обратный вызов прогресса в виде байтов байтов, и удерживать весь контроль над связью через http очень часто.

На моем личном подходе я применяю логику продвижения своего бизнеса только для своих клиентов, избегая делиться другим объектом с конечной точкой.


1
Это на самом деле не отвечает на вопрос о преимуществах / недостатках.
Бенни
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.