AutoResetEvent и несколько наборов - PullRequest
6 голосов
/ 16 декабря 2011

Я пытаюсь спроектировать структуру данных вокруг стека, который блокируется, пока в стеке не будет доступного элемента. Я попытался использовать 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 ]

6 голосов
/ 16 декабря 2011

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

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

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

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


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

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

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

Немедленно добавитьназывается снова.

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

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

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

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

Третий поток разрешен, хотя событие.

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

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

1 голос
/ 30 декабря 2011

Я не проверял решение на основе 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();
        }
    }
}
1 голос
/ 18 декабря 2011

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


EDIT : Есть 2 проблемы с кодом выше:

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

  2. Когда Pop вызывается несколько раз в одном потоке, они разделяют тот же initialState для AutoResetEvent - напр. Push-сигналы 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);
        }
    }
}
...