我正在使用.NET 3.5并且需要一个精简版的BlockingCollection(不一定需要强类型)。
我已经提出了以下功能,这对于我的功能需求来说已经足够了,但我认为会遇到竞争条件:
public class WuaBlockingCollection
{
private const int TimeoutInterval = 50;
private readonly Queue _queue = new Queue();
private readonly AutoResetEvent _event = new AutoResetEvent(false);
private readonly object _queueLock = new object();
public bool IsAddingComplete { get; private set; }
public void Add(object item)
{
lock (_queueLock)
{
if (IsAddingComplete)
throw new InvalidOperationException(
"The collection has been marked as complete with regards to additions.");
_queue.Enqueue(item);
}
_event.Set();
}
public object Take()
{
if (!TryTake(out var obj, Timeout.Infinite))
{
throw new InvalidOperationException(
"The collection argument is empty and has been marked as complete with regards to additions.");
}
return obj;
}
public bool TryTake(out object obj, int timeout)
{
var elapsed = 0;
var startTime = Environment.TickCount;
obj = null;
lock (_queueLock)
{
if (IsAddingComplete && _queue.Count == 0) return false;
}
do
{
var waitTime = timeout - elapsed;
if (waitTime > TimeoutInterval || timeout == Timeout.Infinite)
{
waitTime = TimeoutInterval;
}
if (_event.WaitOne(waitTime))
{
break;
}
} while (timeout == Timeout.Infinite || (elapsed = unchecked(Environment.TickCount - startTime)) < timeout);
if (timeout != Timeout.Infinite && elapsed >= timeout) return false;
var isQueueEmpty = false;
lock (_queueLock)
{
if (_queue.Count == 0)
{
return false;
}
obj = _queue.Dequeue();
if (_queue.Count > 0)
{
isQueueEmpty = true;
}
}
if (!isQueueEmpty)
{
_event.Set();
}
return true;
}
public void CompleteAdding()
{
lock (_queueLock)
{
IsAddingComplete = true;
}
_event.Set();
}
}
更具体地说,在TryTake()
部分的if (!isQueueEmpty)
方法中。
基本上在isQueueEmpty
设置之间以及使用它的值完成之后,另一个线程可能会做一些影响_queue.Count
的事情。
理论上只有Add()
或CompleteAdding()
才能执行此操作(因为运行TryTake()
的多个线程会卡在lock()
或_event.WaitOne()
})但是我不确定这是不是要担心什么,也不确定如何实际修复它而不将_event.Set()
置于lock
内部,我认为这可能会产生不利影响。
如果答案是将_event.Set()
放在lock
内,请说明为什么这不会对事件产生影响以及是否需要进一步修改。