避免竞争条件 - BlockingCollection

时间:2018-03-15 08:49:44

标签: c# multithreading locking

我正在使用.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内,请说明为什么这不会对事件产生影响以及是否需要进一步修改。

0 个答案:

没有答案