这有点棘手。您正确地希望避免中止,因为这可能会在重要操作(写入文件等)中间终止线程。设置该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
等)。因此,如果您使用更手动的方法,例如此答案中的方法,您将无法取消正在进行的长时间运行的计算。它绝对是放在后袋中的好工具,并且可能仅在您的特定情况下有用。