6

我正在尝试围绕堆栈设计一个数据结构,该结构会阻塞直到堆栈有可用的项目。我尝试使用 anAutoResetEvent但我想我误解了同步过程的工作原理。基本上,看下面的代码,当没有可用的东西时,我试图从堆栈中弹出。

似乎它的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 的。

4

3 回答 3

6

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

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

然后,您可以以线程安全的方式访问它,并为您正确完成所有阻塞。看这里

您可以通过调用来使用 sharedStack,sharedStack.Take()然后将阻止获取,直到有东西可以从堆栈中获取。


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

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

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

立即再次调用 Add。

现在通过的第一个线程等待从 Add 获取锁。

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

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

First Get 线程现在需要锁定并弹出。仍然看到堆栈上的一个对象并调用 SET。

尽管该事件允许第三个线程。

第二个获取线程现在需要锁定并弹出。在堆栈中什么都看不到,也不调用 set。

但。太晚了。第三个线程已经被允许通过,所以当第二个线程放弃锁时,第三个线程尝试从空堆栈中弹出并抛出。

于 2011-12-16T19:49:38.290 回答
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 - 例如。AutoResetEvent当添加项目时,推送信号 。现在,当我弹出一个项目时,它第一次运行良好,因为 Stack. 然而第二次,没有任何值,Stack所以它通过调用等待- 但由于调用发出.WaitOne了这个事件的信号,它只会返回 true,而不是像预期的那样等待。AutoResetEventPush

一个(工作)替代方案:

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);
        }
    }
}
于 2011-12-18T12:18:00.130 回答
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();
        }
    }
}
于 2011-12-30T17:45:03.003 回答