C#一旦主线程休眠,所有线程都停止了

时间:2010-02-19 06:30:14

标签: c# multithreading .net-2.0

我有一个像这样运行Producer-Consumer模型的类:

public class SyncEvents
{
    public bool waiting;

    public SyncEvents()
    {
        waiting = true;
    }
}

public class Producer
{
    private readonly Queue<Delegate> _queue;
    private SyncEvents _sync;
    private Object _waitAck;

    public Producer(Queue<Delegate> q, SyncEvents sync, Object obj)
    {
        _queue = q;
        _sync = sync;
        _waitAck = obj;
    }

    public void ThreadRun()
    {
        lock (_sync)
        {
            while (true)
            {
                Monitor.Wait(_sync, 0);
                if (_queue.Count > 0)
                {
                    _sync.waiting = false;
                }
                else
                {
                    _sync.waiting = true;
                    lock (_waitAck)
                    {
                        Monitor.Pulse(_waitAck);
                    }
                }
                Monitor.Pulse(_sync);
            }
        }
    }

}

public class Consumer
{
    private readonly Queue<Delegate> _queue;
    private SyncEvents _sync;

    private int count = 0;

    public Consumer(Queue<Delegate> q, SyncEvents sync)
    {
        _queue = q;
        _sync = sync;
    }

    public void ThreadRun()
    {
        lock (_sync)
        {
            while (true)
            {
                while (_queue.Count == 0)
                {
                    Monitor.Wait(_sync);
                }

                Delegate query = _queue.Dequeue();
                query.DynamicInvoke(null);

                count++;

                Monitor.Pulse(_sync);
            }
        }
    }
}

/// <summary>
/// Act as a consumer to the queries produced by the DataGridViewCustomCell
/// </summary>
public class QueryThread
{
    private SyncEvents _syncEvents = new SyncEvents();
    private Object waitAck = new Object();
    private Queue<Delegate> _queryQueue = new Queue<Delegate>();

    Producer queryProducer;
    Consumer queryConsumer;

    public QueryThread()
    {
        queryProducer = new Producer(_queryQueue, _syncEvents, waitAck);
        queryConsumer = new Consumer(_queryQueue, _syncEvents);

        Thread producerThread = new Thread(queryProducer.ThreadRun);
        Thread consumerThread = new Thread(queryConsumer.ThreadRun);

        producerThread.IsBackground = true;
        consumerThread.IsBackground = true;

        producerThread.Start();
        consumerThread.Start();
    }

    public bool isQueueEmpty()
    {
        return _syncEvents.waiting;
    }

    public void wait()
    {
        lock (waitAck)
        {
            while (_queryQueue.Count > 0)
            {
                Monitor.Wait(waitAck);
            }
        }
    }

    public void Enqueue(Delegate item)
    {
        _queryQueue.Enqueue(item);
    }
}

代码运行顺利但是wait()函数。 在某些情况下,我想等到队列中的所有函数都运行完毕,所以我创建了wait()函数。

制作人将在适当的时间发出waitAck脉冲。

然而,当行“Monitor.Wait(waitAck);”时在wait()函数中运行,所有线程都停止,包括生产者和消费者线程。

为什么会发生这种情况,我该如何解决?谢谢!

2 个答案:

答案 0 :(得分:1)

所有线程似乎不太可能实际停止,但我应该指出,为了避免错误唤醒,你应该有一个while循环而不是if语句:

lock (waitAck)
{
    while(queryProducer.secondQueue.Count > 0)
    {
        Monitor.Wait(waitAck);
    }
}

您正在调用Monitor.Wait这意味着应该释放waitAck,因此它不应该阻止使用者线程锁定...

您能否提供有关生产者/消费者线程“停止”的方式的更多信息?看起来他们刚刚陷入僵局吗?

您的制作人是否使用NotifyNotifyAll?你现在有一个额外的等待线程,所以如果你只使用Notify它只会释放一个线程...如果没有{{1}的详细信息,很难看出这是否是一个问题}和Producer类。

如果你能展示一个简短但完整的程序来证明这个问题,那会有所帮助。

编辑:好的,现在您已经发布了我可以看到许多问题的代码:

  • 拥有如此众多的公共变量是一种灾难。你的类应该封装它们的功能,这样其他代码就不必去寻找实现的部分。 (例如,您的调用代码实际上不应该访问队列。)

  • 您将项目直接添加到第二个队列,这意味着您无法有效唤醒生产者将其添加到第一个队列。为什么你甚至有多个队列?

  • 你总是在生产者线程中等待Consumer ...为什么?什么会开始通知它?一般来说,生产者线程不应该等待,除非你有一个有界缓冲区

  • 您有一个静态变量(_waitAck),每次创建新实例时都会被覆盖。这是个坏主意。

你还没有展示你的_sync课程 - 是否意味着做一些有趣的事情?

老实说,看起来你的设计很奇怪 - 你可能最好从头开始。尝试将整个生产者/消费者队列封装在一个具有SyncEventsProduce方法的类中,以及Consume(或类似的东西)。我认为你会发现同步逻辑更容易。

答案 1 :(得分:1)

以下是我对您的代码的看法:

public class ProducerConsumer
{
    private ManualResetEvent _ready;
    private Queue<Delegate> _queue; 
    private Thread _consumerService;
    private static Object _sync = new Object();

    public ProducerConsumer(Queue<Delegate> queue)
    {
        lock (_sync)
        {
            // Note: I would recommend that you don't even
            // bother with taking in a queue.  You should be able
            // to just instantiate a new Queue<Delegate>()
            // and use it when you Enqueue.  There is nothing that
            // you really need to pass into the constructor.
            _queue = queue;
            _ready = new ManualResetEvent(false);
            _consumerService = new Thread(Run);
            _consumerService.IsBackground = true;
            _consumerService.Start();
        }
    }

    public override void Enqueue(Delegate value)
    {
        lock (_sync)
        {
            _queue.Enqueue(value);
            _ready.Set();
        }
    }

    // The consumer blocks until the producer puts something in the queue.
    private void Run()
    {
        Delegate query;
        try
        {
            while (true)
            {
                _ready.WaitOne();
                lock (_sync)
                {
                    if (_queue.Count > 0)
                    {
                        query = _queue.Dequeue();
                        query.DynamicInvoke(null);
                    }
                    else
                    {
                        _ready.Reset();
                        continue;
                    }
                }
            }
        }
        catch (ThreadInterruptedException)
        {
            _queue.Clear();
            return;
        }
    }


    protected override void Dispose(bool disposing)
    {
        lock (_sync)
        {
            if (_consumerService != null)
            {
                _consumerService.Interrupt();
            }
        }
        base.Dispose(disposing);
    }


}

我不确定你试图通过等待函数实现什么...我假设你试图对可以排队的项目数量设置某种类型的限制。在这种情况下,当队列中有太多项目时,只需抛出异常或返回失败信号,调用Enqueue的客户端将继续重试,直到队列可以占用更多项目。采取乐观的方法将为您节省很多麻烦,它只是帮助您摆脱许多复杂的逻辑。

如果你真的想在那里等待,那么我可以帮你找出更好的方法。让我知道你想要通过等待实现什么,我会帮助你。

注意:我从我的一个项目中获取此代码,稍微修改并在此处发布...可能存在一些轻微的语法错误,但逻辑应该是正确的。

更新:根据您的评论,我做了一些修改:我在课程中添加了另一个ManualResetEvent,因此当您致电BlockQueue()时,它会为您提供一个事件,您可以等待并设置一个标志以阻止Enqueue函数排队更多元素。一旦队列中的所有查询都得到了服务,该标志就会设置为true,并且_wait事件被设置为无论谁在等待它都会得到信号。

public class ProducerConsumer
{
    private bool _canEnqueue;
    private ManualResetEvent _ready;
    private Queue<Delegate> _queue; 
    private Thread _consumerService;

    private static Object _sync = new Object();
    private static ManualResetEvent _wait = new ManualResetEvent(false);

    public ProducerConsumer()
    {
        lock (_sync)
        {
            _queue = new Queue<Delegate> _queue;
            _canEnqueue = true;
            _ready = new ManualResetEvent(false);
            _consumerService = new Thread(Run);
            _consumerService.IsBackground = true;
            _consumerService.Start();
        }
    }

    public bool Enqueue(Delegate value)
    {
        lock (_sync)
        {
            // Don't allow anybody to enqueue
            if( _canEnqueue )
            {
                _queue.Enqueue(value);
                _ready.Set();
                return true;
            }
        }
        // Whoever is calling Enqueue should try again later.
        return false;
    }

    // The consumer blocks until the producer puts something in the queue.
    private void Run()
    {
        try
        {
            while (true)
            {
                // Wait for a query to be enqueued
                _ready.WaitOne();

                // Process the query
                lock (_sync)
                {
                    if (_queue.Count > 0)
                    {
                        Delegate query = _queue.Dequeue();
                        query.DynamicInvoke(null);
                    }
                    else
                    {
                        _canEnqueue = true;
                        _ready.Reset();
                        _wait.Set();
                        continue;
                    }
                }
            }
        }
        catch (ThreadInterruptedException)
        {
            _queue.Clear();
            return;
        }
    }

    // Block your queue from enqueuing, return null
    // if the queue is already empty.
    public ManualResetEvent BlockQueue()
    {
        lock(_sync)
        {
            if( _queue.Count > 0 )
            {
                _canEnqueue = false;
                _wait.Reset();
            }
            else
            {
                // You need to tell the caller that they can't
                // block your queue while it's empty. The caller
                // should check if the result is null before calling
                // WaitOne().
                return null;
            }
        }
        return _wait;
    }

    protected override void Dispose(bool disposing)
    {
        lock (_sync)
        {
            if (_consumerService != null)
            {
                _consumerService.Interrupt();
                // Set wait when you're disposing the queue
                // so that nobody is left with a lingering wait.
                _wait.Set();
            }
        }
        base.Dispose(disposing);
    }
}