【问题标题】:Best way to report thread progress报告线程进度的最佳方式
【发布时间】:2010-10-06 14:41:13
【问题描述】:

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

【问题讨论】:

  • 你是如何创建线程的?
  • @ Steve Townsend - 我有一个可以预先创建它们的工厂和一个控制它们何时运行的状态机。
  • 有什么限制?你可以创建一个类来包装一个线程并使用这个类吗?这是主动对象模式。
  • @Ray Henry - 感谢您的设计模式名称,我仍在学习/对他们不熟悉。限制是我需要能够确保某些线程同时运行并且某些线程按顺序运行(相对于其他线程),具体取决于它们的任务类型和提供的附加信息(本质上是线程矩阵)。我认为包装类是我最好的选择,但也许几个只允许单个工作项的 BackgoundWorkers 也是一种选择。
  • 为每个要同时运行的线程使用不同的 BackgroundWorker。将单个 BackgroundWorker 用于您要按顺序运行的作业。实际上,在 WorkCompleted 处理程序中,您可以设置要运行的下一个 BackgroundWorker。此外,如果您使用的是 .NET 4,请查看 System.Threading.Tasks 命名空间。

标签: c# multithreading backgroundworker threadpool


【解决方案1】:

重载 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 编译器在 while 循环之外“提升”Progress 的读取。这种运动给人一种“陈旧”的印象。这是该问题的半现实演示。

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 中也无法重现,因为它们的优化更原始)。错误是“已停止”永远不会显示,即使它显然应该显示。现在,取消对Thread.MemoryBarrier 的调用并注意行为的变化。还要记住,即使是对这段代码结构的最细微的更改当前也会抑制编译器进行相关优化的能力。其中一项更改是实际调用委托。换句话说,您不能当前使用空检查和调用模式来重现过时的读取问题,但是 CLI 规范中没有 nothing (无论如何我都知道) 禁止未来假设的 JIT 编译器重新应用该“提升”优化。

【讨论】:

  • @Brian:你不妨考虑在var local = ...stackoverflow.com/questions/1773680/…之后移动屏障
  • @andras:不,那会真的混淆人们。在这种情况下,它的放置位置根本不重要。但是,这里的意图是Progress 读取是新鲜的总是(不是下一次,而是现在)。我不太关心thisargs 读取是否在if 语句之外和Progress 读取之上。顺便说一句,我本可以使用lock,但天真的程序员可能会在稍后删除它,因为它看起来毫无意义。这是我真正推荐Thread.MemoryBarrier 的少数场景之一,因为它的意图很明显。
  • @Brian:我猜很多人都搞错了成员:connect.microsoft.com/VisualStudio/feedback/…
  • @andras:是的,记忆障碍绝对是最令人困惑的话题之一(除了美国所得税制度)。我实际上已经阅读了您之前发布的所有链接。最近我什至在 Eric 的博客上发表了评论,要求澄清这一点。
  • @andras:请记住,这里有两种内存模型:一种由硬件(CPU 架构)决定,另一种由软件(CLR)决定。您必须针对的模型是两者中较弱的一个。在这种情况下,它几乎总是 CLR,因为就像你说的,x86/x64 实际上有相当强大的模型。这可能是您不会在 那些 CPU 的编译代码中看到内存栅栏的原因。但这并不能阻止 CLR 进行自己的重新排序。
【解决方案2】:

我前段时间试过这个,它对我有用。

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • +1 - 我将使用 Brian 的解决方案进行快速修复,并最终为我的最终产品转向更多基于偶数的异步模式。感谢您的链接!
    【解决方案4】:

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多