Friday, January 26, 2007

ProgressBar и индикаторы выполнения процесса

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

Что должен содержать ProgressBar и индикаторы процесса выполнения (в логическом порядке, а не в порядке важности):

  • 1. Количественный показатель (в штуках, в байтах и т.д.) общего объема данных, которые требуется обработать.
  • 2. Количественный показатель обработанных данных.
  • 3. Количественный показатель данных, которые осталось обработать.
  • 4. Время, потраченное на обработку данных (из п.2).
  • 5. Ориентировочное время обработки оставшихся данных.
  • 6. Визуальное отображение % обработанных данных (собственно сам ProgressBar).
  • 7. Скорость обработки данных.

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

Насколько ProgressBar хорош для пользователя, настолько он является головной болью для разработчика. Хорошо когда есть один процесс в одном потоке, когда заранее известно, сколько данных надо обработать и когда длина шага обработки является постоянной величиной. Но когда ситуация не столь проста могут появиться следующие проблемы:

  • 1. Заранее не известно, сколько данных должно быть обработано. Эта проблема очень часто возникает при запросах к базе данных, когда заранее не известно, сколько данных должно быть возвращено базой. Так же не редка ситуация, когда в вычислительных задачах перебор элементов делается по большому набору критериев, в результате чего заранее точно не известно, сколько данных надо перебрать. В обоих случаях оценить общее количество данных все-таки можно выполнив предварительные вычисления или предварительный запрос к базе. Это, конечно, займет лишнее время, но в большинстве случаев это окупается, т.к. информированный пользователь - удовлетворенный пользователь, а неинформированный будет злиться на "глупую программу" которая повисла и не понятно чего там делает. Минус такого решения - оно не подходит для задач, критичных к общему времени выполнения. Второй минус - появляется дополнительный процесс предварительного подсчета, которому так же может потребоваться свой ProgressBar.
  • 2. В процессе выполнения операции невозможно (или можно, но с серьезной дополнительной нагрузкой на систему) узнать процент выполнения операции. Тут все зависит от архитектуры системы и вопрос возвращения данных для показа прогресса надо закладывать еще в момент проектирования. Встраивание этого функционала в уже готовую систему может быть очень сложным и может потребовать "перекраивания" взаимодействия классов и модулей системы. Более-менее стандартные решения: при запросах к базе данных отдавать информацию постранично; в задачах, работающих в основном потоке поднимать события обрабатывающим классом; в задачах, работающих в отдельном потоке периодически опрашивать поток или посылать сообщения из потока.
  • 3. Процесс состоит из набора подпроцессов. Здесь есть, где разгуляться фантазии.
    1. Можно сделать единый ProgressBar и показывать пользователю только сколько времени осталось до конца обработки всех процессов.
    2. Можно к единому ProgressBar-у добавить индикатор наименования выполняющегося процесса.
    3. Можно сделать отдельный ProgressBar для каждого процесса. Это хорошо когда процессов всего 2, иначе в окне будет слишком много информации, что будет путать пользователя.
    4. Можно показывать один ProgressBar, но перезапускать его для каждого процесса. Дополнительно к нему будет полезен лог с законченными и незаконченными процессами.
    5. Можно объединить первый и предыдущий варианты и показывать один общий ProgressBar, и второй перезапускаемый для каждого процесса.
  • 4. Выполняется параллельно несколько процессов. Раньше эта проблема появлялась не часто и, в основном, при закачке данных. Для вычислительных задач распараллеливание процесса вычисления при одноядерном процессоре большого смысла не имела, т.к. одна задача занимала весь ресурс процессора (ну или столько, сколько ей отводилось системой) и запуск второй, параллельной, задачи пропорционально тормозил выполнение первой. Сейчас при появлении CoreDuo и Core2Duo (а в дальнейшем ядер, похоже, будет все больше и больше) можно и нужно пускать параллельные процессы в разных потоках, соответственно вопрос отображения в одном ProgressBar-е нескольких операций выполняющихся в разных потоках становится очень актуальным. Если единицы измерения количественных данных всех параллельных операций одинаковые и операции имеют единую логическую базу – можно показывать единый ProgressBar с суммарными данными. Что делать с операциями, логически не совместимыми, покажет будущий опыт, а пока у меня красивого решения для такой ситуации нет.

2 comments:

Anonymous said...

Проблемы у разработчиков (часто) возникают из-за того, что простой элемент ProgressBar пытаются превратить в комбайн, который и проценты показывает, и время считает. Попутно выводит текущий обрабатываемый элемент и... все что придумает разработчик.

Elena Makurochkina said...

Nikolay, у разработчика, в основном, проблема просто передать в GUI информацию об исполнении операции. Собственно сам прогресс бар (если откинуть все остальные индикаторы) – это процент выполнения. И в GUI все равно надо передавать или процент, или количественный показатель обработанных данных (плюс к нему должен быть количественный показатель данных которые надо обработать). Это минимальный набор и именно он является проблемой.

По поводу того, что разработчик пытается превратить прогресс бар в комбайн… Вы когда-нибудь видели как смотрит бухгалтер на программу, которая ему в течении уже 2-х часов считает годовой отчет, и все это время на экране висят часики без какой либо индикации когда же это издевательство закончится? Что-бы бухгалтер не волновался – ему надо показать, что процесс медленно, но движется и именно для этого вводятся описанные мной индикаторы. Это не значит, что надо применять все сразу. Просто надо думать, что пользователю поможет не беспокоиться и видеть, что программа не висит, а делом занимается. Какую конкретно информацию выводить зависит от «темы» приложения и возможностей разработчиков посчитать тот или иной показатель.

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