2016-09-22 4 views
1

Ich versuche, eine Producer/Consumer-Klasse in C# mit Monitor zu implementieren. Die Idee ist, dass der Verbraucher blocken muss, bis der Produzent einen Artikel für den Verbraucher hat, der Produzent aber weiter produzieren soll. Mein Produzent produziert einen Gegenstand und wartet/schläft dann für einige Zeit, bevor er wieder produziert.Warum blockiert mein Produzent manchmal für immer?

Das Problem Ich sah, dass der Produzent nie aus Thread.Sleep(time) aufwacht. Vielleicht gibt es irgendwo eine Deadlock-Situation.

Bitte helfen Sie mir, dies zu verstehen.

Nur als Anmerkung, ich will nicht BlockingCollection verwenden ... Hier ist mein Code ...

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; 
     } 
    } 

Und die Verwendung dieser Klasse ist wie folgt

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

Sie die Linien wechseln sollte '_tProducer.Start();' und '_keepProducing = true;', man weiß ja nie wenn der Thread vor '_keepProducing = true;' oder danach gestartet wird. –

+0

Sind Sie sicher, dass 'Console.WriteLine' nach' Thread.Sleep' nicht ausgeführt wird? – slawekwin

+0

@slawekwin Ja, ich bin sicher, Console.WriteLine wird nicht ausgeführt, das ist was ich nicht verstehe ... –

Antwort

1

Um ein Verbraucher/Hersteller-Muster zu implementieren, empfehle ich BlockingCollection:

Ein kleines Beispiel:

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); 
    } 

Von MSDN:

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

Bietet Sperrung und Fähigkeiten für Thread-sichere Sammlungen begrenzt, die IProducerConsumerCollection implementieren.

Sie können einige Beispiele dort finden:

http://dotnetpattern.com/csharp-blockingcollection

-1

Sie Monitor.Wait(_objLocker); befindet sich in der lock (_objLocker) Aussage. Auf diese Weise kann der Produzent niemals den Verbraucher hinzufügen und signalisieren. Sie sollten die Monitor.Wait(_objLocker); außerhalb der lock (_objLocker)

So etwas bringen: (pseudo)

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

Das ist, was ich dachte auch, aber es ist nicht wie [Monitor.Wait/Pulse] (https://msdn.microsoft.com/en-us/library/ateab679 (v = vs.110) .aspx) funktioniert – slawekwin

+0

Niemals Monitor verwendet. Ich benutze immer Resetevents. Ich könnte falsch annehmen, dass Monitorwarte/-puls gleich funktioniert. –

Verwandte Themen