我已经构建了一个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。 有什么建议吗?
答案 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队列。
尽管如此,虽然我认为我不能对您发布的代码进行全面分析,但我至少可以提供一些指示:
Dequeue
和Dispose
方法存在争用条件。查看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;