AutoResetEvent 和多个 Set
我正在尝试围绕堆栈设计一种数据结构,该数据结构会阻塞,直到堆栈有可用的项目为止。我尝试使用 AutoResetEvent,但我认为我误解了同步过程的工作原理。基本上,看看下面的代码,我试图在没有可用的情况下从堆栈中弹出。
看起来 AutoResetEvent 的行为就像信号量。这是正确的吗?我可以摆脱 BlockingStack.Get()
中的 Set()
并完成它吗?或者这会导致我只使用我的堆栈项目之一的情况。
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();
}
}
}
我的假设是,当一个线程完成 WaitOne()
函数调用时,AutoResetEvent
会重置所有等待线程。然而,似乎有多个线程正在进入。除非我在某个地方搞乱了我的逻辑。
编辑:这是针对 Silverlight 的。
I'm trying to design a data-structure around a stack that blocks until the stack has an item available. I tried using an AutoResetEvent
but I think I misunderstood how that synchronization process works. Basically, looking at the following code, I am trying to Pop from stack when there's nothing available.
It seems that the AutoResetEvent
is behaving like a semaphore. Is that correct? Can I just get rid of the Set()
in BlockingStack.Get()
and be done with it? Or will that result in a situation where I'm only using one of my stack items.
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();
}
}
}
My assumption was the AutoResetEvent
resets for all waiting threads when one gets through on the WaitOne()
function call. However, it seems that multiple threads are getting in. Unless I've messed up my logic somewhere.
EDIT: This is for Silverlight.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
除非您只是想了解线程的工作原理,否则最好使用阻塞集合。这将为您提供一个由堆栈支持的阻塞集合:
然后您可以以线程安全的方式访问它,并为您正确完成所有阻塞。请参阅此处
您可以通过调用
sharedStack.Take来使用sharedStack ()
然后会阻塞获取,直到有东西可以从堆栈中获取。编辑:
我花了一段时间(和两次尝试),但我想我已经解决了你的问题。
考虑一个空堆栈,其中有 3 个线程正在等待该事件。
调用 Add 时,堆栈有一个对象,并且允许一个线程通过该事件。
立即再次调用 Add。
第一个线程现在等待从 Add 获取锁。
Add 将第二个对象添加到堆栈中,并让另一个线程通过该事件。
现在堆栈上有两个对象,事件中有两个线程,都在等待锁。
第一个 Get 线程现在获取锁定并弹出。看到堆栈上仍然有一个对象并调用 SET。
第三个线程允许该事件发生。
第二个 Get 线程现在获取锁定并弹出。在堆栈中看不到任何内容,并且不调用 set。
但。太晚了。第三个线程已经被允许通过,因此当第二个线程放弃锁时,第三个线程尝试从空堆栈中弹出并抛出异常。
You'd be better off using a blocking collection unless you're just trying to understand how threading works. This would give you a blocking collection backed by a stack:
You can then access it in a threadsafe fashion with all the blocking done properly for you. See here
You can use the sharedStack by calling
sharedStack.Take()
which would then block on the take until there is something to take from the stack.Edit:
Took me a while (and two tries) but I've worked out your issue I think.
Consider an empty stack with 3 threads waiting on the event.
Add is called, the stack has one object and one thread is allowed through the event.
Immediately Add is called again.
The first thread through now waits to get the lock from the Add.
The Add adds a second object to the stack and lets another thread through the event.
Now two objects on stack and 2 threads through the event, both waiting on the lock.
First Get thread now takes lock and pops. Sees one object on the stack still and CALLS SET.
Third thread allowed though the event.
Second Get thread now takes lock and pops. Sees nothing in stack and does not call set.
BUT. It's too late. The third thread has already been allowed through, so when the second thread relinquishes the lock the third thread tries to pop from an empty stack and throws.
不,你当前的代码没有任何意义。目前,每次调用Get
方法(.WaitOne
调用)时,您都会阻塞线程。您可能需要类似以下内容:< /del>这个想法是,如果_internalStack
中当前的项目数为 0,那么它应该等待来自Push
方法的信号。一旦收到信号,它就会继续移动并从堆栈中弹出一个项目。编辑:
上面的代码有两个问题:
每当
Pop
阻塞.WaitOne
时,它不会释放对_internalStack
,因此Push
永远无法获得锁。当在同一线程上多次调用
Pop
时,它们共享AutoResetEvent 的初始状态相同 - 例如。推送信号
添加项目时的
AutoResetEvent
。现在当我弹出一个项目时第一次工作正常,因为实际上有一个项目
堆栈。然而第二次,
Stack
中没有任何值,所以它通过在
AutoResetEvent
上调用.WaitOne
来等待 - 但自从对
Push
的调用发出了此事件的信号,它只会返回 true,并且未按预期等待。
一个(有效的)替代方案:
No, your current code makes no sense. At the moment you're blocking the thread everytime theGet
method is invoked (the.WaitOne
call).You probably want something like:The idea is that, if the current number of items in the_internalStack
is 0, then it should wait for a signal from thePush
method. Once it's signaled, it moves on and pops an item from the stack.EDIT:
There's 2 problems with the code above:
Whenever
Pop
blocks with.WaitOne
, it doesn't release the lock on_internalStack
, and thereforePush
can never obtain the lock.When
Pop
is called multiple times on the same thread, they sharethe same initialState for the AutoResetEvent - ex. Push signals the
AutoResetEvent
when an item is added. Now when I Pop an item itworks fine the first time, since there's actually an item in the
Stack
. However the second time, there's no value in theStack
soit waits by calling
.WaitOne
on theAutoResetEvent
- but sincethe call to
Push
signaled this event, it'll just return true, andnot wait as expected.
An (working) alternative:
我没有验证基于
Monitor
的解决方案,但我确实编写了一个基于信号量的解决方案,该解决方案似乎有效:I did not verify the
Monitor
based solution, but I did write a semaphore-based solution that appears to be working: