2016-09-22 2 views
1

Я пытаюсь реализовать класс производителя/потребителя в C# с использованием Monitor. Идеи состоят в том, что потребитель должен блокировать до тех пор, пока у производителя не будет товар для потребителя, но производитель должен продолжать производить. Мой продюсер производит какой-то предмет, а затем просто ждет/спит когда-нибудь, прежде чем производить снова.Почему мой продюсер иногда блокируется?

Проблема, которую я видел, что производитель никогда не просыпается от Thread.Sleep(time). Возможно, где-то есть тупиковая ситуация.

Пожалуйста, помогите мне понять это.

Так же, как примечание, я не хочу использовать BlockingCollection ... Вот мой код ...

public class ProducerConsumerEx 
    { 
     private object _objLocker = new object(); 
     private Thread _tProducer; 
     private Queue<string> _producerQueue; 
     private bool _keepProducing; 
     public ProducerConsumerEx() 
     { 
      _keepProducing = false; 
      _producerQueue = new Queue<string>(); 
      _tProducer = new Thread(Produce); 
      _tProducer.IsBackground = true; 
     } 

     private void Produce() 
     { 
      while (_keepProducing) 
      { 
       Console.WriteLine($"PRODUCER {Thread.CurrentThread.ManagedThreadId} LOOP"); 
       lock (_objLocker) 
       { 
        string item = DateTime.Now.ToString("HH:mm:ss"); 
        _producerQueue.Enqueue(item); 
        Console.WriteLine($"PRODUCER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} Inserted {item}"); 
        Monitor.Pulse(_objLocker); 
        Console.WriteLine($"PRODUCER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} AF Pulse {item}"); 
       } 
       Console.WriteLine($"PRODUCER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} BF Sleep"); 
       Thread.Sleep(10000); 
       Console.WriteLine($"PRODUCER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} AF Sleep"); 
      } 
     } 
     public void Start() 
     { 
      if (!_keepProducing) 
      { 
       _tProducer.Start(); 
       _keepProducing = true; 
      } 
     } 
     public string Consume() 
     { 
      string val = default(string); 
      Console.WriteLine($"CONSUMER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} BF Consume"); 

      lock (_objLocker) 
      { 
       Console.WriteLine($"CONSUMER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} BF Consume Inside"); 
       if (_producerQueue.Count > 0) 
       { 
        val = _producerQueue.Dequeue(); 
       } 
       else 
       { 
        Console.WriteLine($"CONSUMER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} WAITING"); 
        Monitor.Wait(_objLocker); 
        // 
        if (_producerQueue.Count > 0) 
        { 
         val = _producerQueue.Dequeue(); 
        } 
       } 
      } 
      return val; 
     } 
    } 

И использование этого класса, как это

static void Main(string[] args) 
     {   
      ProducerConsumerEx pc = new ProducerConsumerEx(); 
      pc.Start(); 
      while (true) 
      { 
       string t = pc.Consume(); 
       Console.WriteLine($"Main {t}"); 
      }  
     } 
+3

Вы должны переключить строки '_tProducer.Start()' и '_keepProducing = истина;', вы никогда не знаете, если поток запускается до '_keepProducing = true; 'или после. –

+0

Вы уверены, что 'Console.WriteLine' после' Thread.Sleep' не выполняется? – slawekwin

+0

@slawekwin Да, я уверен, Console.WriteLine не выполняется, это то, что я не понимаю ... –

ответ

1

Для реализации шаблона потребитель/производитель, я Recommand с помощью BlockingCollection:

маленький пример:

private BlockingCollection<string> _producerQueue; 

    void Consume() 
    { 
     foreach (var item in _producerQueue.GetConsumingEnumerable()) 
     { 
      //do some work there 
     } 
    } 

    void Produce() 
    { 
     _producerQueue.Add("a string to consume"); 
    } 

    public void Start() 
    { 
     Task.Factory.StartNew(Consume); 
    } 

Из MSDN:

https://msdn.microsoft.com/en-us/library/dd267312(v=vs.110).aspx

Обеспечивает блокирование и ограничивающие возможности для поточно- коллекций, которые реализуют интерфейс IProducerConsumerCollection.

Вы можете найти некоторые примеры есть:

http://dotnetpattern.com/csharp-blockingcollection

-1

Вы Monitor.Wait(_objLocker); находится в lock (_objLocker) заявлении. Таким образом, производитель никогда не может добавлять и сигнализировать потребителя. Вы должны принести Monitor.Wait(_objLocker); за пределами lock (_objLocker)

Что-то вроде: (псевдо)

public string Consume() 
{ 
    string val = default(string); 
    Console.WriteLine($"CONSUMER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} BF Consume"); 

    int count = 0; 

    lock (_objLocker) 
     count = _producerQueue.Count; 

    if (count == 0) 
    { 
     Console.WriteLine($"CONSUMER {DateTime.Now.ToString("HH:mm:ss")} Thread {Thread.CurrentThread.ManagedThreadId} WAITING"); 
     Monitor.Wait(_objLocker); 
    } 

    lock (_objLocker) 
     val = _producerQueue.Dequeue(); 

    return val; 
} 
+0

Вот что я думал тоже, но это не так [Monitor.Wait/Pulse] (https://msdn.microsoft.com/en-us/library/ateab679 (v = vs.110) .aspx) works – slawekwin

+0

Никогда не использовался Monitor. Я всегда использую resetevents. Я могу ошибаться, что монитор wait/pulse работает одинаково. –

Смежные вопросы