【问题标题】:Proper way to have an endless worker thread?拥有无限工作线程的正确方法?
【发布时间】:2010-12-06 17:25:12
【问题描述】:

我有一个需要大量初始化的对象(在一台强大的机器上需要 1-2 秒)。虽然一旦初始化完成一个典型的“工作”只需要大约 20 毫秒

为了防止它在每次应用程序想要使用它时被重新初始化(在典型使用情况下可能是每秒 50 次或根本不初始化几分钟),我决定给它一个工作队列,并且让它在自己的线程上运行,检查队列中是否有任何工作。但是我不完全确定如何使线程在有或没有工作的情况下无限运行。

这就是我目前所拥有的,欢迎任何批评

    private void DoWork()
    {
        while (true)
        {
            if (JobQue.Count > 0)
            {
                // do work on JobQue.Dequeue()
            }
            else
            {
                System.Threading.Thread.Sleep(50);
            }
        }
    }

经过思考:我在想我可能需要优雅地杀死这个线程而不是让它永远运行,所以我想我会添加一个 Job 类型来告诉线程结束。任何关于如何结束这样的线程的想法也很感激。

【问题讨论】:

    标签: c# .net multithreading worker-thread


    【解决方案1】:

    我已经实现了一个后台任务队列,没有使用任何类型的while 循环、脉冲、等待,或者实际上,根本没有接触Thread 对象。它似乎有效。 (我的意思是,在过去的 18 个月里,它在生产环境中每天处理数千个任务而没有任何意外行为。)这是一个具有两个重要属性的类,Queue<Task>BackgroundWorker。重要的方法有以下三种,这里简述:

    private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
       if (TaskQueue.Count > 0)
       {
          TaskQueue[0].Execute();
       }
    }
    
    private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        Task t = TaskQueue[0];
    
        lock (TaskQueue)
        {
            TaskQueue.Remove(t);
        }
        if (TaskQueue.Count > 0 && !BackgroundWorker.IsBusy)
        {
            BackgroundWorker.RunWorkerAsync();
        }
    }
    
    public void Enqueue(Task t)
    {
       lock (TaskQueue)
       {
          TaskQueue.Add(t);
       }
       if (!BackgroundWorker.IsBusy)
       {
          BackgroundWorker.RunWorkerAsync();
       }
    }
    

    并不是没有等待和脉动。但这一切都发生在BackgroundWorker 内部。每当有任务被放入队列时,它就会唤醒,一直运行到队列为空,然后重新进入睡眠状态。

    我远非线程专家。如果使用BackgroundWorker 就可以解决这样的问题,是否有理由与System.Threading 混为一谈?

    【讨论】:

    • 总的来说,我同意 BackgroundWorker 类通常是管理后台任务的更简单的选择。在这种情况下,我不得不质疑你的一些主张。您正在使用线程对象:lock 关键字是 System.Threading.Monitor.Enter()System.Threading.Monitor.Exit() 的语法糖。您的 RunWorkerCompleted 事件处理程序调用 BackgroundWorker.RunWorkerAsync(),而不是 while 循环。我认为在这种情况下,等待/脉冲 while 循环的逻辑可能更容易理解。
    • 很公平。它可能对我来说看起来更简单,因为我没有等待/脉冲的经验。
    【解决方案2】:

    获取Parallel Framework。它有一个BlockingCollection<T>,您可以将其用作作业队列。你会如何使用它:

    1. 创建将保存您的任务/作业的 BlockingCollection
    2. 创建一些具有永无止境的循环的线程 (while(true){ // 让工作脱离队列)
    3. 设置线程
    4. 当工作可用时将工作添加到集合中

    线程将被阻塞,直到集合中出现一个项目。轮到谁都会得到它(取决于 CPU)。我现在正在使用它,效果很好。

    它还具有依靠 MS 编写多线程访问同一资源的特别讨厌的代码的优势。每当你可以让别人写你应该去写。当然,假设他们拥有比您更多的技术/测试资源和综合经验。

    【讨论】:

    • 您是指标有“此 CTP 仅用于测试目的。”的 CTP?不确定这是一个很好的建议......在 4.0 中,很好 - 但那是测试版!
    【解决方案3】:

    如果你真的不需要让线程退出(只是希望它不让你的应用程序继续运行),你可以将Thread.IsBackground 设置为 true,它会在所有非后台线程结束时结束。 Will 和 Marc 在处理队列方面都有很好的解决方案。

    【讨论】:

      【解决方案4】:

      在大多数情况下,我的做法与您的设置方式非常相似,但语言不同。我有使用数据结构(在 Python 中)的优势,该结构将阻塞线程,直到将项目放入队列,从而无需 sleep 调用。

      如果 .NET 提供这样的类,我会考虑使用它。线程阻塞比在睡眠调用上旋转的线程要好得多。

      您可以通过的工作可以像“null”一样简单;如果代码收到一个空值,它就知道是时候跳出这段时间回家了。

      【讨论】:

        【解决方案5】:

        您需要一个同步原语,例如 WaitHandle(查看静态方法)。这样,您可以“通知”工作线程有工作。它检查队列并继续工作,直到队列为空,此时它等待互斥体再次发出信号。

        让其中一个作业项也成为退出命令,以便您可以在工作线程退出线程时发出信号

        【讨论】:

          【解决方案6】:

          需要lock,所以你可以WaitPulse

          while(true) {
              SomeType item;
              lock(queue) {
                  while(queue.Count == 0) {
                      Monitor.Wait(queue); // releases lock, waits for a Pulse,
                                           // and re-acquires the lock
                  }
                  item = queue.Dequeue(); // we have the lock, and there's data
              }
              // process item **outside** of the lock
          }
          

          添加类似:

          lock(queue) {
              queue.Enqueue(item);
              // if the queue was empty, the worker may be waiting - wake it up
              if(queue.Count == 1) { Monitor.PulseAll(queue); }
          }
          

          您可能还想查看this question,它限制了队列的大小(如果太满则阻塞)。

          【讨论】:

          • 经典队列、锁和脉冲。
          • @Will:AFAIK,pfx 的唯一下载是 CTP - 即不完全支持。在 4.0(目前是测试版)中,是的:TPL 等将是给定的 - 但我们仍然在这里得到 很多 的 2.0 问题...
          • 马克,你能详细介绍一下 Monitor.Wait 的情况吗?这看起来很有趣
          • @Neil - 对,对;我从最初的问题中保留了这一点,但是是的 - 停止/排水会很好。
          • 很好的答案,但是我知道 Pulse 和 PulseAll 被认为是一个错误。具体来说,如果您在内核调度程序线程切换期间执行一个脉冲,您可能会错过一个脉冲。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-07-12
          • 1970-01-01
          • 2014-01-27
          • 1970-01-01
          相关资源
          最近更新 更多