代码之家  ›  专栏  ›  技术社区  ›  James Dunne

后台使用者线程生存期管理最佳实践

  •  2
  • James Dunne  · 技术社区  · 14 年前

    我有一个C#类库,它启动一个后台使用者线程(懒洋洋地),侦听生产者/使用者队列中要完成的任务。此类库可以从任何类型的.NET应用程序中使用,并且当前正在ASP.NET MVC网站。在请求进入队列之前,使用者线程大部分时间都被阻塞。对于任何给定的应用程序域,应该只有一个使用者线程。

    我希望能够在应用程序退出时优雅地关闭使用者线程,而不管它是什么类型的应用程序,例如Windows窗体、控制台或WPF应用程序。应用程序代码本身应该忽略这样一个事实:这个线程正在等待终止。

    如何从类库的角度解决线程生存期问题?是否有一些全局应用程序关闭事件,我可以连接到拆除线程优雅呢?

    Abort() 当控制台应用程序退出时打电话,但坦白说,这很恶心。

    3 回复  |  直到 13 年前
        1
  •  2
  •   Steven Sudit    14 年前

    设置线程的 IsBackground true . 这样就不会阻止这个过程的结束。

    http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

    或者,不使用 Abort ,使用 Interrupt . 更不恶心。

        2
  •  2
  •   Brian Gideon    14 年前

    这有点棘手。您希望避免中止的想法是正确的,因为中止可能会在重要操作(写入文件等)的中间终止线程。设置 IsBackground

    BlockingCollection 班级。不过不用担心。您只需修改自定义队列,以便它定期轮询停止信号。这里是一个快速而肮脏的实现,我刚刚使用阻塞队列的规范实现作为起点。

    public class CancellableBlockingCollection<T>
    {
        private Queue<T> m_Queue = new Queue<T>();
    
        public T Take(WaitHandle cancel)
        {
            lock (m_Queue)
            {
                while (m_Queue.Count <= 0)
                {
                    if (!Monitor.Wait(m_Queue, 1000))
                    {
                        if (cancel.WaitOne(0))
                        {
                            throw new ThreadInterruptedException();
                        }
                    }
                }
                return m_Queue.Dequeue();
            }
        }
    
        public void Add(T data)
        {
            lock (m_Queue)
            {
                m_Queue.Enqueue(data);
                Monitor.Pulse(m_Queue);
            }
        }
    }
    

    注意 Take WaitHandle 可以测试取消信号。这可能与代码其他部分中使用的等待句柄相同。这里的想法是等待句柄在内部的某个特定间隔上被轮询 采取 是可以的,因为它是在一个安全点,因为消费者不可能在一个重要的操作过程中。此时,您可以允许线程在出现异常时分解。以下是您的消费线程可能看起来像。

    ManualResetEvent m_Cancel = new ManualResetEvent(false);
    CancellableBlockingCollection<object> m_Queue = new CancellableBlockingCollection<object>();
    
    private void ConsumerThread()
    {
      while (true)
      {
        object item = m_Queue.Take(m_Cancel); // This will throw if the event is signalled.
      }
    }
    

    有很多其他方法可以取消 采取 等待句柄 ,但使用一个简单的 bool 例如标志。

    更新:

    正如史蒂文在评论中指出的那样 Thread.Interrupt 基本上已经做到了。它将导致线程在阻塞调用上抛出异常…这和我在本例中所追求的差不多,但代码要多得多。一个警告 线程。中断 它只在.NETBCL中的屏蔽调用(如 WaitOne 等)。因此,如果您使用更手动的方法(如本答案中的方法),您将无法取消正在进行的长时间运行的计算。这绝对是一个很好的工具,放在你的后口袋,可能只是在你的特定场景有用。

        3
  •  1
  •   Scott Chamberlain    14 年前

    如果您使用的是.net 4.0 a CancelationToken 可以用来向线程发出信号,表示该正常关闭了。它非常有用,因为大多数Wait命令允许您向它传递一个令牌,如果线程处于等待状态,当线程被取消时,它将自动退出等待。