报告线程进度的最佳方式

时间:2010-10-06 14:41:13

标签: c# multithreading backgroundworker threadpool

我有一个程序,它使用线程顺序执行耗时的进程。我希望能够监视每个线程的进度,类似于BackgroundWorker.ReportProgress / ProgressChanged模型的方式。由于我所处的其他限制,我无法使用ThreadPoolBackgroundWorker。允许/公开此功能的最佳方法是什么。重载Thread类并添加属性/事件?另一个更优雅的解决方案?

4 个答案:

答案 0 :(得分:11)

  

重载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);
    }
  }
}

<强>更新

让我更清楚一下为什么在这种情况下需要记忆障碍。屏障可防止在其他指令之前移动读取。最可能的优化不是来自CPU,而是来自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;
    }
}

必须在没有vshost进程的情况下运行Release构建。任何一个都将禁用显示错误的优化(我相信这在框架版本1.0和1.1中也不可重现,因为它们更原始的优化)。错误是“Stopped”永远不会显示,即使它显然应该是。现在,取消对Thread.MemoryBarrier的调用,注意行为的变化。还要记住,即使对此代码当前的结构进行的最微妙的更改也会阻止编译器进行优化的能力。一个这样的改变是实际调用委托。换句话说,你不能当前使用null检查后跟一个调用模式重现陈旧的读取问题,但CLI规范中有 nothing (我知道无论如何) )禁止未来的假设JIT编译器重新应用“提升”优化。

答案 1 :(得分:1)

我前一段时间尝试过,这对我有用。

  1. 使用锁创建一个类似List的类。
  2. 让您的线程将数据添加到您创建的类的实例中。
  3. 在表单中或要记录日志/进度的位置放置计时器。
  4. Timer.Tick事件中编写代码以读取线程输出的消息。

答案 2 :(得分:1)

您可能还想查看Event-based Asynchronous Pattern

答案 3 :(得分:0)

为每个线程提供一个返回状态对象的回调。您可以使用线程的ManagedThreadId来跟踪单独的线程,例如将其用作Dictionary<int, object>的键。您可以从线程处理循环中的多个位置调用回调,或者从线程内发出的计时器中调用它。

您还可以在回调上使用return参数来指示线程暂停或暂停。

我使用了回调取得了巨大的成功。