后台消费者线程生命周期管理最佳实

时间:2010-08-24 16:30:45

标签: c# .net multithreading queue producer-consumer

我有一个C#类库,它启动一个后台使用者线程(懒惰),它监听从生产者/消费者队列中完成的任务。此类库可以在任何类型的.NET应用程序中使用,目前在ASP.NET MVC网站下使用。消费者线程在大多数情况下被阻塞,直到请求进入队列。对于任何给定的应用程序域,应该只有一个消费者线程。

我希望能够在应用程序退出时正常关闭使用者线程,而不管它是什么类型的应用程序,例如Windows窗体,控制台或WPF应用程序。应用程序代码本身应该不知道这个线程在等待被终止时挥之不去。

如何从类库的角度解决这个线程生命周期问题?是否有一些全局应用程序关闭事件我可以绑定以优雅地拆除线程?

现在,由于它位于ASP.NET MVC应用程序域中,因此无论如何都不会优雅地拆除它们。但是现在我开始在计划终止的控制台应用程序任务中使用它,我想一​​劳永逸地解决这个问题。控制台应用程序不会终止,因为使用者线程仍然处于活动状态并且阻塞等待请求。我已经在类上公开了一个公共静态Thread属性,以便在控制台应用程序退出时发出Abort()调用,但坦率地说,这很恶心。

任何指针都将不胜感激!同样,我不想编写Windows窗体或WPF或控制台应用程序特定的代码来解决问题。一个很好的通用解决方案,每个类库的使用者都可以使用它。

3 个答案:

答案 0 :(得分:2)

将线程的IsBackground属性设置为true。然后它不会阻止该过程结束。

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

或者,不要使用Abort,而是使用Interrupt。更不那么恶心了。

答案 1 :(得分:2)

这有点棘手。您正确地想要避免中止,因为这可能会在重要操作(写入文件等)的过程中终止线程。设置IsBackground属性会产生相同的效果。

您需要做的是使阻止队列可取消。太糟糕了,你没有使用.NET 4.0,否则你可以使用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。这可能与代码的其他部分中使用的等句柄相同。这里的想法是等待句柄在Take内的某个时间间隔内被轮询,如果它被发出信号,则整个方法抛出。假设在Take内投掷是可以的,因为它处于安全点,因为消费者不可能处于重要操作的中间。此时您可以允许线程在异常时解体。这是您的消费者线程的样子。

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

您可以通过其他各种方式取消Take方法。我选择使用WaitHandle,但可以使用简单的bool标记轻松完成。

<强>更新

史蒂文在评论中指出Thread.Interrupt基本上已经这样做了。它会导致一个线程在阻塞调用上抛出一个异常......几乎就是我在这个例子中所追求的,但代码更多。对Thread.Interrupt的一个警告是,它仅适用于.NET BCL中的固定阻塞调用(如WaitOne等)。因此,您将无法取消正在进行的长时间运行计算,就像您使用更像这个答案的手动方法一样。它绝对是一个很好的工具,可以放在你的后袋里,可能只对你的特定场景有用。

答案 2 :(得分:1)

如果您使用的是.net 4.0,可以使用CancelationToken向线程发出信号,告知它是时候正常关闭了。它非常有用,因为大多数Wait命令允许你传递一个令牌,如果线程在线程被取消时等待,它将自动退出等待。