AutoResetEvent和多个集合

时间:2011-12-16 19:37:23

标签: c# data-structures concurrency stack autoresetevent

我正在尝试围绕堆栈设计数据结构,直到堆栈有可用项目为止。我尝试使用AutoResetEvent,但我认为我误解了同步过程的工作原理。基本上,看下面的代码,我试图在没有任何可用的情况下从堆栈弹出。

似乎AutoResetEvent的行为类似于信号量。那是对的吗?我可以摆脱Set()中的BlockingStack.Get()并完成它吗?或者这会导致我只使用我的一个堆栈项目。

public class BlockingStack
{
    private Stack<MyType> _internalStack;
    private AutoResetEvent _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<MyType>(5);
        _blockUntilAvailable = new AutoResetEvent(false);

        for (int i = 0; i < 5; ++i)
        {
            var obj = new MyType();
            Add(obj);
        }
    }

    public MyType Get()
    {
        _blockUntilAvailable.WatiOne();

        lock (_internalStack)
        {
            var obj = _internalStack.Pop();
            if (_internalStack.Count > 0)
            {
                _blockUntilAvailable.Set(); // do I need to do this?
            }

            return obj;
        }
    }

    public void Add(MyType obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);
            _blockUntilAvailable.Set();
        }
    }
}

我的假设是当AutoResetEvent函数调用通过时,所有等待线程的WaitOne()重置。然而,似乎有多个线程进入。除非我在某个地方弄乱了我的逻辑。

编辑:这适用于Silverlight。

3 个答案:

答案 0 :(得分:6)

除非您只是想了解线程的工作原理,否则最好使用阻止集合。这将为您提供由堆栈支持的阻塞集合:

ConcurrentStack<SomeType> MyStack = new ConcurrentStack<SomeType>();
BlockingCollection<SomeType> SharedStack = new BlockingCollection<SomeType>(MyStack)

然后,您可以以线程安全的方式访问它,并为您完成所有阻止。见here

您可以通过调用sharedStack.Take()来使用sharedStack,然后会阻止此操作,直到从堆栈中获取内容为止。


编辑: 花了一段时间(和两次尝试),但我认为我已经解决了你的问题。

考虑一个有3个线程等待事件的空堆栈。

调用Add,堆栈有一个对象,并允许一个线程通过事件。

立即再次调用添加。

第一个线程现在等待从Add获取锁定。

Add将第二个对象添加到堆栈中,让另一个线程通过该事件。

现在堆栈上有两个对象,事件中有两个线程,都在等待锁定。

首先获取线程现在需要锁定和弹出。在堆栈中看到一个对象仍然是CALLS SET。

通过事件允许第三个线程。

第二个Get线程现在需要锁定和弹出。在堆栈中看不到任何内容,也不会调用set。

BUT。太晚了。第三个线程已被允许通过,因此当第二个线程放弃锁定时,第三个线程会尝试从空堆栈弹出并抛出。

答案 1 :(得分:1)

不,你当前的代码毫无意义。每当调用Get方法(.WaitOne调用)时,您就会阻塞该线程。

你可能想要这样的东西:

public class BlockingStack<T>
{
    private Stack<T> _internalStack;
    private AutoResetEvent _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<T>(5);
        _blockUntilAvailable = new AutoResetEvent(false);
    }

    public T Pop()
    {
        lock (_internalStack)
        {
            if (_internalStack.Count == 0)
                _blockUntilAvailable.WaitOne();

            return _internalStack.Pop();
        }
    }

    public void Push(T obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);

            if(_internalStack.Count == 0)
                _blockUntilAvailable.Set();
        }
    }
}

这个想法是,如果_internalStack中的当前项目数为0,那么它应该等待来自Push方法的信号。一旦发出信号,它就会移动并从堆栈中弹出一个项目。


修改: 上面的代码有两个问题:

  1. 每当Pop阻止.WaitOne时,它都不会释放锁定 _internalStack,因此Push永远无法取得锁定。

  2. 在同一个线程上多次调用Pop时,它们会共享 AutoResetEvent的相同initialState - ex。推信号 添加项目时AutoResetEvent。现在当我弹出一个项目时 第一次工作正常,因为实际上有一个项目 Stack。然而第二次,Stack没有价值 它会在.WaitOne上调用AutoResetEvent来等待 - 但是从那以后 对Push的调用标志着此事件,它将返回true,并且 没有按预期等待。

  3. (工作)替代方案:

    public class BlockingStack<T>
    {
        private Stack<T> _internalStack;
    
        public BlockingStack()
        {
            _internalStack = new Stack<T>(5);
        }
    
        public T Pop()
        {
            lock (_internalStack)
            {
                if (_internalStack.Count == 0)
                    Monitor.Wait(_internalStack);
    
                return _internalStack.Pop();
            }
        }
    
        public void Push(T obj)
        {
            lock (_internalStack)
            {
                _internalStack.Push(obj);
                Monitor.Pulse(_internalStack);
            }
        }
    }
    

答案 2 :(得分:1)

我没有验证基于Monitor的解决方案,但我确实编写了一个似乎有效的基于信号量的解决方案:

public class Semaphore
{
    private int _count;
    private int _maximum;
    private object _countGuard;

    public Semaphore(int maximum)
    {
        _count = 0;
        _maximum = maximum;
        _countGuard = new object();
    }

    public void WaitOne()
    {
        while (true)
        {
            lock (_countGuard)
            {
                if (_count < _maximum)
                {
                    _count++;
                    return;
                }
            }
            Thread.Sleep(50);
        }
    }

    public void ReleaseOne()
    {
        lock (_countGuard)
        {
            if (_count > 0)
            {
                _count--;
            }
        }
    }
}

public class BlockingStack
{
    private Stack<MyType> _internalStack;
    private Semaphore _blockUntilAvailable;

    public BlockingStack()
    {
        _internalStack = new Stack<MyType>(5);
        _blockUntilAvailable = new Semaphore(5);

        for (int i = 0; i < 5; ++i)
        {
            var obj = new MyType();
            Add(obj);
        }
    }

    public MyType Get()
    {
        _blockUntilAvailable.WaitOne();

        lock (_internalStack)
        {
            var obj = _internalStack.Pop();
            return obj;
        }
    }

    public void Add(MyType obj)
    {
        lock (_internalStack)
        {
            _internalStack.Push(obj);
            _blockUntilAvailable.ReleaseOne();
        }
    }
}