【问题标题】:Producer Consumer queue does not dispose生产者消费者队列不释放
【发布时间】:2011-04-22 14:39:57
【问题描述】:

我已经建立了一个生产者消费者队列,该队列包装了一个 .net 4.0 的 ConcurrentQueue,其中 SlimManualResetEvent 信号在生产(Enqueue)和消费(while(true)线程之间)。 队列看起来像:

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 的集合。 有什么建议吗?

【问题讨论】:

  • 有什么理由不使用BlockingCollection<T>
  • 因为我需要一个队列。在 ConcurrentQueue 上使用 BlockingCollection 有什么好处?我猜它仍然会阻止加入消费线程。
  • 我没有给你解决方法,但原因是:当你设置事件时,一个或多个线程可以被唤醒。不能保证他们会全部醒来。

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


【解决方案1】:

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

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

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

我的代码正确吗?我不会因为太自信而答应;毕竟,我还没有编写单元测试,没有对其进行任何诊断等等。这只是一个基本的草稿,让您了解使用BlockingCollection&lt;T&gt;而不是ConcurrentQueue&lt;T&gt;如何清理您的很多逻辑(在我看来(内部队列的行为)。


评论中提出的问题:

您不使用BlockingCollection&lt;T&gt;的任何原因?

你的答案:

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

来自MSDN documentation on the default constructor for the BlockingCollection&lt;T&gt; class

默认的基础集合是ConcurrentQueue&lt;T&gt;

如果您选择实现自己的类而不是使用BlockingCollection&lt;T&gt;唯一原因是您需要一个 FIFO 队列,那么……您可能需要重新考虑您的决定。使用默认无参数构造函数实例化的BlockingCollection&lt;T&gt;一个 FIFO 队列。

也就是说,虽然我认为我无法对您发布的代码进行全面分析,但我至少可以提供几点建议:

  1. 对于处理这种棘手的多线程行为的类,我会非常犹豫是否要以您在这里的方式使用事件。调用代码可以附加它想要的任何事件处理程序,而这些事件处理程序又会抛出异常(您没有捕获)、长时间阻塞,甚至可能由于完全超出您控制的原因而死锁——这在阻塞队列的情况。
  2. DequeueDispose 方法中存在竞争条件。

看看你的Dequeue方法的这些行:

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

现在看看Dispose的这两行代码:

_IsActive = false;

_mres.Set(); // point B

假设您有三个线程,T1、T2 和 T3。 T1 和 T2 都在 A 点,每个检查 _IsActive 并找到 true。然后Dispose被调用,T3设置_IsActivefalse(但是T1和T2已经过了点A),然后到达点 B,它调用_mres.Set()。然后 T1 到达点 C,移动到点 D,并调用_mres.Reset()。现在 T2 到达点 C 并且将永远被卡住,因为_mres.Set 将不再被调用(任何执行Enqueue 的线程都会找到_IsActive == false 并立即返回,并且执行Dispose的线程已经通过点B)。

我很乐意尝试提供一些帮助来解决这个竞争条件,但我怀疑BlockingCollection&lt;T&gt; 实际上并不是你需要的类。如果你能提供更多信息让我相信情况并非如此,也许我会再看看。

【讨论】:

  • 嗨。谢谢您的帮助。我猜比赛条件是问题。你能帮忙解决吗?我仍然没有得到并发队列上 BlockingCollection 的优势,它如何帮助我解决问题。
  • @user437631:BlockingCollection&lt;T&gt; 的优势在于它提供了您需要的功能并且没有错误!您的ProduerConsumerQueue&lt;T&gt; 课程有哪些BlockingCollection&lt;T&gt; 无法获得的功能?很少值得去实现一个组织(微软)已经实现的东西,并且有资源来构建、测试和彻底记录它。如果你能解释BlockingCollection&lt;T&gt;没有的你需要什么,我一定会帮助你的。但是说“我没有优势”对我来说并没有说服力。
  • 您好,再次感谢您的帮助。我的意思是我正在使用的 ConcurrentQueue 上 BlockingCollection 的优势。为了允许生产者消费者模式,我需要一个消费线程和线程之间的信号。因为我在我的服务中使用了其中的一些,所以我构建了上面的通用类来包装消费者生产者线程。我也需要使用 BlockingCollection 这样做,不是吗?你能给我一个你的意思的样本吗?
【解决方案2】:

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

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

private volatile bool _IsActive=true;

【讨论】:

  • 我将 _IsActive 标记为 volatile,但问题仍然存在。
猜你喜欢
  • 2012-01-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多