我正在尝试围绕堆栈设计数据结构,直到堆栈有可用项目为止。我尝试使用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。
答案 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
方法的信号。一旦发出信号,它就会移动并从堆栈中弹出一个项目。
修改强>: 上面的代码有两个问题:
每当Pop
阻止.WaitOne
时,它都不会释放锁定
_internalStack
,因此Push
永远无法取得锁定。
在同一个线程上多次调用Pop
时,它们会共享
AutoResetEvent的相同initialState - ex。推信号
添加项目时AutoResetEvent
。现在当我弹出一个项目时
第一次工作正常,因为实际上有一个项目
Stack
。然而第二次,Stack
没有价值
它会在.WaitOne
上调用AutoResetEvent
来等待 - 但是从那以后
对Push
的调用标志着此事件,它将返回true,并且
没有按预期等待。
(工作)替代方案:
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();
}
}
}