2

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

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

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

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

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

4

3 回答 3

2

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

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

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

于 2010-08-24T16:34:16.063 回答
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可以测试取消信号的 a。这可能与代码其他部分中使用的等待句柄相同。这里的想法是等待句柄在内部的某个时间间隔内被轮询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方法。我选择使用 a WaitHandle,但它可以很容易地使用一个简单的bool标志来完成,例如。

更新:

正如史蒂文在评论中指出的那样,Thread.Interrupt基本上已经做到了这一点。它会导致线程在阻塞调用上抛出异常……这和我在这个例子中所追求的差不多,但是有更多的代码。需要注意的一点Thread.Interrupt是,它仅在 .NET BCL 中的固定阻塞调用期间有效(例如 .NETWaitOne等)。因此,如果您使用更手动的方法,例如此答案中的方法,您将无法取消正在进行的长时间运行的计算。它绝对是放在后袋中的好工具,并且可能仅在您的特定情况下有用。

于 2010-08-24T20:19:50.970 回答
1

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

于 2010-08-24T16:40:08.510 回答