生产者消费者队列不处置

时间:2010-10-07 21:33:16

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

我已经构建了一个Producer Consumer队列,它包装了.net 4.0的ConcurrentQueue,并在生成(Enqueue)和使用(while(true)线程)之间使用SlimManualResetEvent信号。 队列看起来像:

public class ProducerConsumerQueue<T> : IDisposable, IProducerConsumerQueue<T>
{
    private bool _IsActive=true;

    public int Count
    {
        get
        {
            return this._workerQueue.Count;
        }
    }

    public bool IsActive
    {
        get { return _IsActive; }
        set { _IsActive = value; }
    }

    public event Dequeued<T> OnDequeued = delegate { };
    public event LoggedHandler OnLogged = delegate { };

    private ConcurrentQueue<T> _workerQueue = new ConcurrentQueue<T>();

    private object _locker = new object();

    Thread[] _workers;

    #region IDisposable Members

    int _workerCount=0;

    ManualResetEventSlim _mres = new ManualResetEventSlim();

    public void Dispose()
    {
        _IsActive = false;

        _mres.Set();

        LogWriter.Write("55555555555");

          for (int i = 0; i < _workerCount; i++)
          // Wait for the consumer's thread to finish.
          {
             _workers[i].Join();        
          }
           LogWriter.Write("6666666666");
     // Release any OS resources.
    }
    public ProducerConsumerQueue(int workerCount)
    {
        try
        {
            _workerCount = workerCount;
            _workers = new Thread[workerCount];
            // Create and start a separate thread for each worker
            for (int i = 0; i < workerCount; i++)
                (_workers[i] = new Thread(Work)).Start();
        }
        catch (Exception ex)
        {
            OnLogged(ex.Message + ex.StackTrace);
        }

    }
    #endregion

    #region IProducerConsumerQueue<T> Members

    public void EnqueueTask(T task)
    {
        if (_IsActive)
        {
            _workerQueue.Enqueue(task);
            //Monitor.Pulse(_locker);
            _mres.Set();
        }
    }

    public void Work()
    {
      while (_IsActive)
      {
          try
          {
              T item = Dequeue();
              if (item != null)
                  OnDequeued(item);
          }
          catch (Exception ex)
          {
              OnLogged(ex.Message + ex.StackTrace);
          }              
      }
    }

    #endregion
    private T Dequeue()
    {
        try
        {
            T dequeueItem;
            //if (_workerQueue.Count > 0)
            //{
            _workerQueue.TryDequeue(out dequeueItem);
            if (dequeueItem != null)
                return dequeueItem;
            //}
            if (_IsActive)
            {
                _mres.Wait();
                _mres.Reset();
            }
            //_workerQueue.TryDequeue(out dequeueItem);
            return dequeueItem;
        }
        catch (Exception ex)
        {
            OnLogged(ex.Message + ex.StackTrace);
            T dequeueItem;
            //if (_workerQueue.Count > 0)
            //{
            _workerQueue.TryDequeue(out dequeueItem);
            return dequeueItem;
        }

    }


    public void Clear()
    {
        _workerQueue = new ConcurrentQueue<T>();
    }
}

}

当调用Dispose时,它有时会阻塞连接(一个线程消耗)并且dispose方法被卡住了。我猜它会停留在等待resetEvents但是为此我调用了dispose上的set。 有什么建议吗?

2 个答案:

答案 0 :(得分:2)

更新:我理解您在内部需要队列的观点。我建议使用BlockingCollection<T>是基于以下事实:您的代码包含许多逻辑来提供阻塞行为。自己编写这样的逻辑非常容易出错(我从经验中知道这一点);因此,当框架中存在一个至少为你完成某些工作的现有类时,通常最好继续使用它。

有关如何使用BlockingCollection<T>实现此类的完整示例有点太大而无法包含在此答案中,因此我发布了一个有效的example on pastebin.com;随便看看你的想法。

我还写了一个示例程序,演示了上面的例子here

我的代码是否正确?我不会说很有信心;毕竟,我还没有编写单元测试,对其运行任何诊断等等。这只是一个基本的草案,让您了解如何使用BlockingCollection<T>代替ConcurrentQueue<T>来清理您的大部分逻辑(在我看来)并且更容易关注您的类的主要目的(从队列中消耗项目并通知订阅者)而不是其实现的某个困难方面>(内部队列的阻塞行为)。


评论中提出的问题:

  

您没有使用BlockingCollection<T>的任何原因?

你的回答:

  

[...]我需要一个队列。

来自MSDN documentation on the default constructor for the BlockingCollection<T> class

  

默认的基础集合是ConcurrentQueue<T>

如果您选择实现自己的类而不是使用BlockingCollection<T>原因是您需要FIFO队列,那么......您可能想重新考虑您的决定。使用默认无参数构造函数实例化的BlockingCollection<T> FIFO队列。

尽管如此,虽然我认为我不能对您发布的代码进行全面分析,但我至少可以提供一些指示:

  1. 我非常犹豫是否会以你在这里处理这种棘手的多线程行为的方式使用事件。调用代码可以附加它想要的任何事件处理程序,这些可以反过来抛出异常(你没有捕获),长时间阻塞,甚至可能因为完全超出你控制范围的原因而死锁 - 这是非常糟糕的阻塞队列的情况。
  2. 您的DequeueDispose方法存在争用条件。
  3. 查看Dequeue方法的这些行:

    if (_IsActive) // point A
    {
        _mres.Wait(); // point C
        _mres.Reset(); // point D
    }
    

    现在来看看Dispose中的这两行:

    _IsActive = false;
    
    _mres.Set(); // point B
    

    假设您有三个主题,T 1 ,T 2 和T 3 。 T 1 和T 2 都在点 A ,其中每个都检查_IsActive并找到true。然后调用Dispose,并且T 3 _IsActive设置为false(但是T 1 且T 2 已经通过了 A 点,然后到达 B 点,在那里调用_mres.Set()。然后T 1 指向 C ,继续指向 D ,并调用_mres.Reset()。现在T 2 到达点 C 并且将永远停留,因为_mres.Set将不再被调用(执行Enqueue的任何线程都会找到{{1}并立即返回,执行_IsActive == false的线程已经通过了 B 点。

    我很乐意尝试在解决这种竞争条件方面提供一些帮助,但我怀疑Dispose实际上并不是你需要的类。如果你能提供更多信息来说服我不是这样的话,也许我会再看看。

答案 1 :(得分:0)

由于_IsActive未标记为volatile且所有访问都没有lock,因此每个核心都可以为此值设置单独的缓存,并且该缓存可能永远不会刷新。因此,在_IsActive中将Dispose标记为false实际上不会影响所有正在运行的线程。

http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

private volatile bool _IsActive=true;