AutoResetEvent 和多个 Set

发布于 2024-12-21 09:52:26 字数 1355 浏览 1 评论 0原文

我正在尝试围绕堆栈设计一种数据结构,该数据结构会阻塞,直到堆栈有可用的项目为止。我尝试使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

拥抱我好吗 2024-12-28 09:52:26

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

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

然后您可以以线程安全的方式访问它,并为您正确完成所有阻塞。请参阅此处

您可以通过调用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:

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

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.

蘑菇王子 2024-12-28 09:52:26

不,你当前的代码没有任何意义。目前,每次调用 Get 方法(.WaitOne 调用)时,您都会阻塞线程。

您可能需要类似以下内容:< /del>

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 的初始状态相同 - 例如。推送信号
    添加项目时的AutoResetEvent。现在当我弹出一个项目时
    第一次工作正常,因为实际上有一个项目
    堆栈。然而第二次,Stack 中没有任何值,所以
    它通过在 AutoResetEvent 上调用 .WaitOne 来等待 - 但自从
    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);
        }
    }
}

No, your current code makes no sense. At the moment you're blocking the thread everytime the Get method is invoked (the .WaitOnecall).

You probably want something like:

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();
        }
    }
}

The idea is that, if the current number of items in the _internalStack is 0, then it should wait for a signal from the Push method. Once it's signaled, it moves on and pops an item from the stack.


EDIT:
There's 2 problems with the code above:

  1. Whenever Pop blocks with .WaitOne, it doesn't release the lock on
    _internalStack, and therefore Push can never obtain the lock.

  2. When Pop is called multiple times on the same thread, they share
    the same initialState for the AutoResetEvent - ex. Push signals the
    AutoResetEvent when an item is added. Now when I Pop an item it
    works fine the first time, since there's actually an item in the
    Stack. However the second time, there's no value in the Stack so
    it waits by calling .WaitOne on the AutoResetEvent - but since
    the call to Push signaled this event, it'll just return true, and
    not wait as expected.

An (working) alternative:

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);
        }
    }
}
橙味迷妹 2024-12-28 09:52:26

我没有验证基于 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();
        }
    }
}

I did not verify the Monitor based solution, but I did write a semaphore-based solution that appears to be working:

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();
        }
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文