报告线程进度的最佳方式

发布于 2024-09-26 04:38:28 字数 245 浏览 6 评论 0原文

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

I have a program that uses threads to perform time-consuming processes sequentially. I want to be able to monitor the progress of each thread similar to the way that the BackgroundWorker.ReportProgress/ProgressChanged model does. I can't use ThreadPool or BackgroundWorker due to other constraints I'm under. What is the best way to allow/expose this functionality. Overload the Thread class and add a property/event? Another more-elegant solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

快乐很简单 2024-10-03 04:38:28

重载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 进程的情况下运行发布版本。任何一个都会禁用显示错误的优化(我相信这在框架版本 1.0 和 1.1 中也无法重现,因为它们的优化更原始)。错误在于“已停止”从未显示,即使它显然应该显示。现在,取消对 Thread.MemoryBarrier 的调用的注释并注意行为的变化。另请记住,即使是对此代码结构进行最细微的更改,目前也会抑制编译器进行相关优化的能力。其中一项更改是实际调用委托。换句话说,您目前无法使用空检查和调用模式来重现过时的读取问题,但 CLI 规范中没有任何内容(我无论如何都知道这一点) ),禁止未来假设的 JIT 编译器重新应用该“提升”优化。

Overload the Thread class and add a
property/event?

If by "overload" you actually mean inherit then no. The Thread is sealed so it cannot be inherited which means you will not be able to add any properties or events to it.

Another more-elegant solution?

Create a class that encapsulates the logic that will be executed by the thread. Add a property or event (or both) which can be used to obtain progress information from it.

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

Update:

Let me be a little more clear on why a memory barrier is necessary in this situation. The barrier prevents the read from being moved before other instructions. The most likely optimization is not from the CPU, but from the JIT compiler "lifting" the read of Progress outside of the while loop. This movement gives the impression of "stale" reads. Here is a semi-realistic demonstration of the problem.

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;
    }
}

It is imperative that a Release build is ran without the vshost process. Either one will disable the optimization that manifest the bug (I believe this is not reproducable in framework version 1.0 and 1.1 as well due to their more primitive optimizations). The bug is that "Stopped" is never displayed even though it clearly should be. Now, uncomment the call to Thread.MemoryBarrier and notice the change in behavior. Also keep in mind that even the most subtle changes to the structure of this code currently inhibit the compiler's ability to make the optimization in question. One such change would be to actually invoke the delegate. In other words you cannot currently reproduce the stale read problem using the null check followed by an invocation pattern, but there is nothing in the CLI specification (that I am aware of anyway) that prohibits a future hypothetical JIT compiler from reapplying that "lifting" optimization.

森林很绿却致人迷途 2024-10-03 04:38:28

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

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

I tried this some time ago and it worked for me.

  1. Create a List-like class with locks.
  2. Have your threads add data to an instance of the class you created.
  3. Place a timer in your Form or wherever you want to record the log/progress.
  4. Write code in the Timer.Tick event to read the messages the threads output.
转瞬即逝 2024-10-03 04:38:28

您可能还想查看基于事件的异步模式

You might also want to check out the Event-based Asynchronous Pattern.

梦里的微风 2024-10-03 04:38:28

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

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

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

Provide each thread with a callback that returns a status object. You can use the thread's ManagedThreadId to keep track of separate threads, such as using it as a key to a Dictionary<int, object>. You can invoke the callback from numerous places in the thread's processing loop or call it from a timer fired from within the thread.

You can also use the return argument on a callback to signal the thread to pause or halt.

I've used callbacks with great success.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文