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 ответа

Решение

Я не проверял 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();
        }
    }
}

Было бы лучше использовать блокирующую коллекцию, если вы просто не пытаетесь понять, как работает многопоточность. Это даст вам блокирующую коллекцию, поддерживаемую стеком:

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

После этого вы можете получить к нему доступ в защищенном потоке, и все блокировки будут выполнены для вас должным образом. Смотрите здесь

Вы можете использовать sharedStack, позвонив sharedStack.Take() который затем будет блокироваться при получении, пока не будет что-то взять из стека.


Редактировать: Мне потребовалось время (и две попытки), но я решил вашу проблему, я думаю.

Рассмотрим пустой стек с 3 потоками, ожидающими события.

Вызов Add, стек имеет один объект и один поток разрешен через событие.

Немедленно Add снова вызывается.

Первый поток через сейчас ожидает получения блокировки от Add.

Add добавляет второй объект в стек и пропускает другой поток через событие.

Теперь два объекта в стеке и два потока через событие, оба ожидающие блокировки.

Сначала Get Thread теперь берет блокировку и трещит. Еще видит один объект в стеке и CALLS SET.

Третий поток допускается через событие.

Второй поток Get теперь берет блокировку и выскакивает. Ничего не видит в стеке и не вызывает set.

НО. Слишком поздно. Третий поток уже пропущен, поэтому, когда второй поток снимает блокировку, третий поток пытается выскочить из пустого стека и выбросить.

Нет, ваш текущий код не имеет смысла. В данный момент вы блокируете поток каждый раз, когда 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 метод. Как только он получает сигнал, он движется дальше и вытаскивает предмет из стека.


РЕДАКТИРОВАТЬ: есть 2 проблемы с кодом выше:

  1. Всякий раз, когда Pop блоки с .WaitOne, это не снимает блокировку на_internalStack, и поэтому Push никогда не сможет получить блокировку.

  2. когда Pop вызывается несколько раз в одном и том же потоке, они используют один и тот же initialState для AutoResetEvent - ex. Push-сигналыAutoResetEvent когда элемент добавлен. Теперь, когда я высовываю элемент, он работает нормально с первого раза, так как на самом делеStack, Однако во второй раз, нет никакой ценности в Stack так что жду звонка .WaitOne на AutoResetEvent - но с момента звонка Push сигнализировал об этом событии, он просто вернет истину и не будет ждать, как ожидалось.

(Рабочая) альтернатива:

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);
        }
    }
}
Другие вопросы по тегам