Вы правы. Вызов WaitOne
внутри замка может привести к тупику. И проверку, чтобы убедиться, что список пуст, нужно сделать внутри блокировки, иначе может быть гонка с другим потоком, пытающимся удалить элемент. Теперь ваш код выглядит подозрительно как шаблон «производитель-потребитель», который обычно реализуется с помощью очереди блокировки. Если вы используете .NET 4.0, вы можете воспользоваться классом BlockingCollection .
Однако позвольте мне рассказать о нескольких способах, которыми вы можете сделать это сами. Первый использует List
и ManualResetEvent
, чтобы продемонстрировать, как это можно сделать, используя структуры данных в вашем вопросе. Обратите внимание на использование цикла while
в методе Take
.
public class BlockingJobsCollection
{
private List<Job> m_List = new List<Job>();
private ManualResetEvent m_Signal = new ManualResetEvent(false);
public void Add(Job item)
{
lock (m_List)
{
m_List.Add(item);
m_Signal.Set();
}
}
public Job Take()
{
while (true)
{
lock (m_List)
{
if (m_List.Count > 0)
{
Job item = m_List.First();
m_List.Remove(item);
if (m_List.Count == 0)
{
m_Signal.Reset();
}
return item;
}
}
m_Signal.WaitOne();
}
}
}
Но это не то, как я бы это сделал. Я хотел бы пойти с более простым решением ниже с использованием Monitor.Wait
и Monitor.Pulse
. Monitor.Wait
полезен, потому что можно вызывать внутри блокировки. На самом деле, предположим, должно быть сделано таким образом.
public class BlockingJobsCollection
{
private Queue<Job> m_Queue = new Queue<Job>();
public void Add(Job item)
{
lock (m_Queue)
{
m_Queue.Enqueue(item);
Monitor.Pulse(m_Queue);
}
}
public Job Take()
{
lock (m_Queue)
{
while (m_Queue.Count == 0)
{
Monitor.Wait(m_Queue);
}
return m_Queue.Dequeue();
}
}
}