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 проблемы с кодом выше:
Всякий раз, когда
Pop
блоки с.WaitOne
, это не снимает блокировку на_internalStack
, и поэтомуPush
никогда не сможет получить блокировку.когда
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);
}
}
}