Отслеживание прогресса рекурсивного метода - PullRequest
5 голосов
/ 02 августа 2009

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

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

Но с помощью рекурсивного метода, повторяющего древовидную структуру, я не обязательно знаю, сколько узлов у дерева в начале. Должен ли я сначала рекурсивно прочитать дерево и просто посчитать все узлы, прежде чем запускать рекурсивный метод, который делает все, что я хочу? Или, может быть, просто отслеживать текущую сумму, когда узлы добавляются или удаляются из дерева? Есть ли лучший вариант?

Ответы [ 6 ]

3 голосов
/ 02 августа 2009

Распространенным подходом к этой проблеме является отображение индикатора выполнения, который изменяется, но не обновляется фактический процент. Например, маленький значок загрузки в Firefox - это круговая вещь, которая просто вращается - вы знаете, что она делает что-то , но вы не знаете, сколько времени это займет. Может быть, просто сделайте это и добавьте сообщение, что «это может занять некоторое время в зависимости от того, сколько данных есть ...»

1 голос
/ 01 мая 2019

Если стоимость обхода дерева ниже по сравнению со стоимостью фактической операции, то стоит предварительно рассчитать данные о ходе выполнения.

Самый простой способ

Как вы описали, подсчитывает все узлы, а затем отслеживает, сколько узлов было завершено по мере применения операции.

Достаточно хорошо для 99% случаев. Предостережение заключается в том, что если операция выполняет некоторое сокращение (перепрыгивание через ненужные узлы и т. Д.), То количество узлов не является точным отображением прогресса, поскольку скачкообразные узлы не вносят вклад в подсчет. Таким образом, планка может подскочить с 60% до 100% после завершения. В приведенном ниже примере процесс переходит узлы ниже B

     A
    / \
   /   \
 (B)    F
 /|\   /|\
C D E G H I

Индикатор выполнения укажет:

[Node] [True Progress] [Displayed]
A           10%           10%
 B          20%           20%
 F          60%           30%
  G         70%           40%
  H         80%           50%
  I         90%           60%
  -        100%          100%

Более точный способ использования ориентиров

Вы можете размещать ориентиры во время начальной настройки. Отслеживайте первые 200 элементов в списке. Когда вы нажмете 200, отпустите все остальные объекты в списке (чтобы вернуться к 100 предметам). Теперь следите за каждым встреченным узлом. Когда вы нажмете 400 найденных узлов, список увеличится до 200 объектов. Вы снова отпускаете друг друга, но с этого момента следите за 1 из 4 объектов и т. Д.

Таким образом, список ориентиров колеблется между 100/200 узлами. Когда дерево полностью проанализировано, в списке может быть ~ 120 объектов, равномерно распределенных по дереву. Теперь вы можете начать операцию. При обнаружении узла проверьте, является ли он списком. Если это так, вы можете запустить индикатор выполнения на позицию этого элемента (если элемент # 84, то прогресс равен 84/120 = 70%

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

Все числа настраиваются, поэтому размер списка ориентиров можно увеличить для большей точности / реактивности.

Используя тот же пример, со списком из 5 ориентиров

              Node Landmarks Steps Comments
              A    [A----]   1     -
              B    [AB---]   1     -
     A        C    [ABC--]   1     -
    / \       D    [ABCD-]   1     -
   /   \      E    [ABCDE]   1     -
 (B)    F     F    [BDF--]   2     Cleanup of A&C&E
 /|\   /|\    G    [BDF--]   2     G is skipped
C D E G H I   H    [BDFH-]   2     -
              I    [BDFH-]   2     Done

Мы получаем список из 5 ориентиров, каждый из которых представляет 20%

Теперь при выполнении операции (которая полностью пропускает B) индикатор выполнения будет показывать:

[Node] [True Progress] [Displayed]
A           10%            0%
 B          20%           20%
 F          60%           60%
  G         70%           60%
  H         80%           80%
  I         90%           80%
  -        100%          100%

Улучшение

Можно отслеживать наименьшего общего предка (LCA) из двух последовательных ориентиров. Если операция выходит из LCA, она может сделать вывод о том, что она прошла следующий ориентир, что дает некоторую информацию о ходе выполнения, даже если ориентир № 85 был пропущен. Поведение изменяется между входом и выходом из узла: вы регистрируетесь, входя в ориентир и выходя из LCA

              Node Landmarks  LCA   Steps Comments
              A    [A----]   [----] 1     -
              B    [AB---]   [A---] 1     A is LCA of A&B
     A        C    [ABC--]   [AB--] 1     B is LCA of B&C
    / \       D    [ABCD-]   [ABB-] 1     B is LCA of C&D
   /   \      E    [ABCDE]   [ABBB] 1     B is LCA of D&E
 (B)    F     F    [BDF--]   [BA--] 2     Cleanup, recompute LCAs
 /|\   /|\    G    [BDF--]   [BA--] 2     Skipped
C D E G H I   H    [BDFH-]   [BAF-] 2     F is LCA of F&H
              I    [BDFHI]   [BAFF] 2     Should be skipped, is Registered as last node of the tree (F is LCA of H&I)

Теперь при выполнении операции (которая полностью пропускает B) индикатор выполнения будет показывать:

  [Node] [True Progress] [Displayed] [Tracked ] [Tracked] [Comments]
                                     [Landmark] [  LCA  ]
->A           10%            0%        B          -        Tracking landmark B...
   ->B        20%           20%        D          B        Found Tracked Landmark B, Now tracking D (LCA of D,B is B)
    (B)       20%           20%        D          B        (Process skips Nodes C/D/E)
   <-B        60%           40%        F          A        Exiting LCA B, must have jumped over Landmark D, now tracking F
  A           60%           40%        F          A        -
   ->F        60%           60%        H          F        Entering Landmark F, now tracking H (LCA of F,H is F)
      ->G     70%           60%        H          F        -
      ->H     80%           80%        I          F        Found Tracked Landmark H, now tracking I (LCA if H, I is F)
      ->I     90%          100%                            Found Tracked Landmark I (last item), operation should be complete
1 голос
/ 17 октября 2012

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

Предполагая, что вы можете сказать, сколько объектов у вас есть на каждом уровне вашего дерева (что должно быть возможно, так как это простой подсчет), ваше сообщение будет отображать что-то вроде этого, когда оно работает с первым объектом 50 предметов на уровне 1:

Обработка: Уровень1 - 1/50

Как только он вызывается рекурсивно и начинает искать уровень 2 для текущего объекта уровня 1, сообщение расширяется, добавляя статус второго уровня:

Обработка: Уровень 1 - 1/50, Уровень 2 - 1/25

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

Обработка: Уровень 1 - 1/50, Уровень 2 - 2/25

Как только вы закончите со вторым уровнем для текущего объекта уровня 1, вы удалите последний раздел сообщения и вернетесь к вызывающему уровню, и перейдете ко второму объекту на этом уровне:

Обработка: Уровень1 - 2/50

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

Если ваше дерево очень глубокое, это сообщение может стать очень длинным. Там может быть более чистый способ сделать это.

Кроме того, я бы не просто редактировал сообщения все время. Я бы оставил единственный (в случае, если индикация прогресса обновляется несколькими потоками) с внутренним списком уровней и их статусов, и просто ToString (), чтобы сохранить вещи в чистоте.

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

1 голос
/ 02 августа 2009

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

Например, если бы я просматривал оргструктуру, я бы знал, что мои конечные пользователи знакомы с большинством людей на оргструктуре, поэтому я бы использовал имена людей. Например, если я сообщу Бобу, который сообщает Джо, который сообщает Сью, когда моя запись обрабатывается, лейбл скажет что-то вроде ..

Currently processing Sue\Joe\Bob\David

Таким образом, вы обновляете текст метки состояния на каждом узле. Просто убедитесь, что вы вызываете Application.DoEvents () после изменения текста метки для обновления экрана. Если вы действительно хотите показать, как далеко вы находитесь, индикатор выполнения будет лучше, но этот вариант мне подходит в подобных ситуациях. Это дает некоторую обратную связь относительно того, что происходит, и пользовательский опыт - это то, что действительно имеет значение.

Однако одно предостережение заключается в том, что обновление текста метки и вызов Application.DoEvents () на самом деле замедляет обработку, , но обычно окупается, потому что пользователи могут видеть, что происходит, и знать, что программа не просто "замерз".

1 голос
/ 02 августа 2009

Вы можете сделать несколько вещей. Вы можете настроить структуру данных так, чтобы каждый узел сохранял размер дерева с корнем в этом узле. Кроме этого:

  • Если деревья, как правило, большие, то отдельный прогон для определения размера дерева может быть не очень хорошей идеей с точки зрения производительности (особенно, если дерево не полностью помещается в кэш).

  • Если деревья, как правило, достаточно сбалансированы, то после обработки k -го поддерева в общей сложности n поддеревьев вы прошли примерно k / n. * 100 процентов узлов. Вы можете использовать эти знания как показатель прогресса.

0 голосов
/ 02 августа 2009

Хороший индикатор прогресса будет основан на количестве узлов в сумме (путем рекурсивного подсчета). Раздражающим, но, возможно, более эффективным индикатором прогресса будет тот, который увеличивает MaxValue с каждым шагом рекурсии (делая ваши шаги прогресса мельче с ходом). Эти методы можно объединить, сделав приблизительную оценку, прежде чем запускать метод и обновлять его по мере продвижения.

Я полагаю, что подход Microsoft заключается в использовании индикатора выполнения "от начала до конца в цикле".

...