我在ASP.NET中有一个生产者-消费者场景,我设计了一个Producer类、一个Consumer类和一个用于保存共享对象的类,并负责生产者和消费者之间的通信,让我们称之为Mediator。因为我在启动时(在父对象中)发送执行路径,一个线程将调用Producer.Start(),另一个线程将调用Consumer.Start(),因此需要将Mediator的引用传递给Producer和Consumer (通过Constructor)。Mediator是一个智能类,它将优化许多事情,比如它的内部队列的长度,但目前将它视为一个循环阻塞队列。Producer会将新对象排队到Mediator,直到队列满了,然后Producer会阻塞。Consumer将对象从Mediator中排出,直到队列中没有任何内容。对于线程之间的信令,我在Mediator类中实现了两个方法:Wait()和Pulse()。代码是这样的:
Class Mediator
{
private object _locker = new object();
public void Wait()
{
lock(_locker)
Monitor.Wait(_locker);
}
public void Pulse()
{
lock(_locker)
Monitor.Pulse(_locker);
}
}
// This way threads are signaling:
Class Consumer
{
object x;
if (Mediator.TryDequeue(out x))
// Do something
else
Mediator.Wait();
}在中介程序中,我每次都使用this.Pulse(),这样等待线程就会收到信号并继续工作。
但是我遇到了死锁,因为我从来没有使用过这种设计来发送消息,所以我不确定是设计出了什么问题,还是在其他地方做错了什么?
谢谢
发布于 2010-10-19 03:06:00
设计没有什么不对。
当您使用Monitor.Wait()和Monitor.Pulse()时,当您不知道哪个线程将首先执行它的工作时(生产者还是消费者),就会产生问题。在这种情况下,使用AutoResetEvent解决问题。当它到达应该使用生产者生成的数据的部分时,请考虑消费者。也许它在生产者发出信号之前就到达了那里,那么一切都很好,但是如果消费者在生产者发出信号之后到达那里呢?是的,然后您会遇到一个死锁,因为生产者已经为该部分调用了Monitor.Pulse(),并且不会重复它。使用AutoResetEvent,您可以确定使用者在那里等待生产者发出的信号,如果生产者在使用者到达该部分之前已经发出信号,则门是打开的,并且使用者将继续。
在Mediator中使用Monitor.Wait()和Monitor.Pulse()来发送等待线程的消息是可以的。
发布于 2010-10-18 13:04:17
这里没有太多的代码可以继续,但我最好的猜测是,您有一个活锁问题。如果Mediator.Pulse在Mediator.Wait之前被调用,那么即使队列中有什么东西,信号也会丢失。下面是实现阻塞队列的标准模式。
public class BlockingQueue<T>
{
private Queue<T> m_Queue = new Queue<T>();
public void Enqueue(T item)
{
lock (m_Queue)
{
m_Queue.Enqueue(item);
Monitor.Pulse(m_Queue);
}
}
public T Dequeue()
{
lock (m_Queue)
{
while (m_Queue.Count == 0)
{
Monitor.Wait(m_Queue);
}
return m_Queue.Dequeue();
}
}
}注意,只有当队列为空时才调用Monitor.Wait。还请注意它是如何在while循环中调用的。这是因为Wait没有比Enter更高的优先级,所以进入Dequeue的新线程可能会占用最后一项,即使对Wait的调用已经准备好返回。如果没有循环,线程可以尝试从空队列中删除项。
发布于 2010-10-18 01:48:33
如果您可以使用.NET 4,最好的选择是使用BlockingCollection<T> (http://msdn.microsoft.com/en-us/library/dd267312.aspx)来处理排队、退队列和队列长度的限制。
https://stackoverflow.com/questions/3956127
复制相似问题