Лучший способ сообщить о продвижении темы - PullRequest
6 голосов
/ 06 октября 2010

У меня есть программа, которая использует потоки для последовательного выполнения трудоемких процессов. Я хочу иметь возможность отслеживать прогресс каждого потока аналогично тому, как это делает модель BackgroundWorker.ReportProgress / ProgressChanged. Я не могу использовать ThreadPool или BackgroundWorker из-за других ограничений, на которые я нахожусь. Каков наилучший способ разрешить / выставить эту функциональность. Перегрузить класс Thread и добавить свойство / событие? Еще одно более элегантное решение?

Ответы [ 4 ]

11 голосов
/ 06 октября 2010

Перегрузить класс Thread и добавить свойство / событие?

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

Еще одно более элегантное решение?

Создайте класс, который инкапсулирует логику, которая будет выполняться потоком.Добавьте свойство или событие (или оба), которые можно использовать для получения информации о прогрессе.

public class Worker
{
  private Thread m_Thread = new Thread(Run);

  public event EventHandler<ProgressEventArgs> Progress;

  public void Start()
  {
    m_Thread.Start();
  }

  private void Run()
  {
    while (true)
    {
      // Do some work.

      OnProgress(new ProgressEventArgs(...));

      // Do some work.
    }
  }

  private void OnProgress(ProgressEventArgs args)
  {

    // Get a copy of the multicast delegate so that we can do the
    // null check and invocation safely. This works because delegates are
    // immutable. Remember to create a memory barrier so that a fresh read
    // of the delegate occurs everytime. This is done via a simple lock below.
    EventHandler<ProgressEventArgs> local;
    lock (this)
    {
      var local = Progress;
    }
    if (local != null)
    {
      local(this, args);
    }
  }
}

Обновление:

Позвольте мне быть немного более яснымпочему барьер памяти необходим в этой ситуации.Барьер предотвращает перемещение чтения перед другими инструкциями.Наиболее вероятная оптимизация не от ЦП, а от JIT-компилятора, «поднимающего» чтение Progress за пределы цикла while.Это движение создает впечатление «несвежих» чтений.Вот полуреалистичная демонстрация проблемы.

class Program
{
    static event EventHandler Progress;

    static void Main(string[] args)
    {
        var thread = new Thread(
            () =>
            {
                var local = GetEvent();
                while (local == null)
                {
                    local = GetEvent();
                }
            });
        thread.Start();
        Thread.Sleep(1000);
        Progress += (s, a) => { Console.WriteLine("Progress"); };
        thread.Join();
        Console.WriteLine("Stopped");
        Console.ReadLine();
    }

    static EventHandler GetEvent()
    {
        //Thread.MemoryBarrier();
        var local = Progress;
        return local;
    }
}

Крайне важно, чтобы сборка Release запускалась без процесса vshost.Любой из них отключит оптимизацию, которая обнаруживает ошибку (я думаю, что это не воспроизводится в версиях фреймворка 1.0 и 1.1, а также из-за их более примитивной оптимизации).Ошибка заключается в том, что «Остановлено» никогда не отображается, даже если оно явно должно быть.Теперь раскомментируйте вызов Thread.MemoryBarrier и обратите внимание на изменение поведения.Также имейте в виду, что даже самые тонкие изменения в структуре этого кода в настоящее время ограничивают способность компилятора выполнять данную оптимизацию.Одним из таких изменений будет фактический вызов делегата.Другими словами, вы не можете в настоящее время воспроизвести проблему устаревшего чтения, используя нулевую проверку, сопровождаемую шаблоном вызова, но в спецификации CLI есть (1029 * ничто ) (о котором я все равно знаю), чтозапрещает будущему гипотетическому JIT-компилятору повторно применять эту «подъемную» оптимизацию.

1 голос
/ 06 октября 2010

Возможно, вы также захотите проверить Асинхронный шаблон на основе событий .

1 голос
/ 06 октября 2010

Я попробовал это некоторое время назад, и у меня это сработало.

  1. Создать List -подобный класс с блокировками.
  2. Ваши потоки должны добавить данные в экземпляр созданного вами класса.
  3. Поместите таймер в форму или там, где вы хотите записать журнал / прогресс.
  4. Введите код в событии Timer.Tick для чтения сообщений, выводимых потоками.
0 голосов
/ 06 октября 2010

Предоставьте каждому потоку обратный вызов, который возвращает объект состояния.Вы можете использовать поток ManagedThreadId для отслеживания отдельных потоков, например, использовать его как ключ к Dictionary<int, object>.Вы можете вызывать обратный вызов из множества мест в цикле обработки потока или вызывать его из таймера, запущенного из потока.

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

Я с большим успехом использовал обратные вызовы.

...