2010-09-26 6 views
6

Ich habe eine generische Erzeuger-Verbraucher-Warteschlange entwickelt, die in der folgenden Art und Weise von Monitor-Impulse:benötigt Monitor.Wait Synchronisation?

der Enqueue:

public void EnqueueTask(T task) 
    { 
     _workerQueue.Enqueue(task); 
     Monitor.Pulse(_locker); 
    } 

die dequeue:

private T Dequeue() 
    { 
     T dequeueItem; 
     if (_workerQueue.Count > 0) 
     { 
       _workerQueue.TryDequeue(out dequeueItem); 
      if(dequeueItem!=null) 
       return dequeueItem; 
     } 
     while (_workerQueue.Count == 0) 
      { 
       Monitor.Wait(_locker); 
     } 
     _workerQueue.TryDequeue(out dequeueItem); 
     return dequeueItem; 
    } 

die Warte Abschnitt erzeugt die folgende SynchronizationLockException: "Objektsynchronisierungsmethode wurde von einem unsynchronisierten Codeblock aufgerufen" Muss ich es synchronisieren? Warum ? Ist es besser, ManualResetEvents oder die Slim-Version von .NET 4.0 zu verwenden?

Antwort

6

Ja, der aktuelle Thread muss den Monitor "besitzen", um entweder oder Pulse, wie dokumentiert, aufzurufen. (Also müssen Sie auch für Pulse sperren.) Ich weiß nicht, die Details für warum es erforderlich ist, aber es ist das gleiche in Java. Normalerweise habe ich jedoch herausgefunden, dass ich das trotzdem tun möchte, um den Anrufcode sauber zu machen.

Beachten Sie, dass Wait gibt den Monitor selbst, dann für die Pulse wartet, zurückkauft dann den Monitor vor der Rückkehr.

Wie für die Verwendung von ManualResetEvent oder AutoResetEvent statt - Sie könnten, aber persönlich lieber mit ich die Monitor Methoden, wenn ich einige der anderen Merkmale der Wartegriffe (wie atomar warten auf jede/alle mehrere Griffe) benötigen.

+0

warum bevorzugen Sie es? Wie würdest du den Monitor synchronisieren, nur eine Sperre für das Locker-Objekt, das für den Monitor verwendet wird? Wird durch die Sperre keine weitere Kontextumschaltung hinzugefügt, die ResetEvents nicht benötigt? – user437631

+0

@ user437631: Ja, nur eine normale 'lock' Anweisung ist in Ordnung. Das kann einen zusätzlichen Kontextwechsel erfordern oder nicht - und ich denke nicht, dass Sie irgendwelche Beweise dafür haben, dass ResetEvents dies nicht benötigen würde. Da es sich um CLR-interne Objekte und nicht um prozessübergreifende Win32-Objekte handelt, sind Monitore leichter als ResetEvents. –

2

aus der MSDN Beschreibung Monitor.Wait():

hebt die Sperre auf ein Objekt und blockiert den aktuellen Thread, bis es die Verriegelung zurückkauft.

Der 'löst die Sperre' Teil ist das Problem, das Objekt ist nicht gesperrt. Sie behandeln das _locker-Objekt, als wäre es ein WaitHandle. Es ist eine Form von schwarzer Magie, die am besten unserem Medizinmann, Jeffrey Richter und Joe Duffy, überlassen wird. Aber ich werde diesen einen Schuß geben:

public class BlockingQueue<T> { 
    private Queue<T> queue = new Queue<T>(); 

    public void Enqueue(T obj) { 
     lock (queue) { 
      queue.Enqueue(obj); 
      Monitor.Pulse(queue); 
     } 
    } 

    public T Dequeue() { 
     T obj; 
     lock (queue) { 
      while (queue.Count == 0) { 
       Monitor.Wait(queue); 
      } 
      obj = queue.Dequeue(); 
     } 
     return obj; 
    } 
} 

In den meisten praktischen Producer/Consumer-Szenario mögen Sie den Hersteller drosseln, damit es nicht in die Warteschlange unbegrenzt füllen kann. Überprüfen Sie Duffys BoundedBuffer design für ein Beispiel. Wenn Sie es sich leisten können, auf .NET 4.0 umzusteigen, dann möchten Sie definitiv die ConcurrentQueue-Klasse nutzen, da sie viel mehr schwarze Magie mit geringem Overhead-Locking und Spin-Warten bietet.

0

Der richtige Weg Monitor.Wait und Monitor.Pulse/PulseAll ist nicht so über ein Instrument des Wartens zu sehen, sondern (für Wait) als ein Mittel, das System wissen zu lassen, dass der Code in einer Warteschleife ist, die nicht Ausfahrt bis sich etwas von Interesse ändert, und (für Pulse/PulseAll) als ein Mittel, das System wissen zu lassen, dass Code gerade etwas geändert hat, das die Ausgangsbedingung erfüllen könnte, die Warteschleife eines anderen Threads. Man sollte in der Lage sein, alle Vorkommen von Wait durch Sleep(0) zu ersetzen, und Code funktioniert immer noch korrekt (auch wenn es viel weniger effizient ist, da die CPU-Zeit wiederholt Bedingungen ausgibt, die sich nicht geändert haben).

Für diesen Mechanismus zu arbeiten, ist es notwendig, die Möglichkeit der folgenden Sequenz zu vermeiden:

  • Der Code in der Warteschleife testet die Bedingung, wenn es nicht erfüllt ist.

  • Der Code in einem anderen Thread ändert die Bedingung so, dass sie erfüllt ist.

  • Der Code in diesem anderen Thread pulsiert die Sperre (auf die noch niemand wartet).

  • Der Code in der Warteschleife führt einen Wait aus, da seine Bedingung nicht erfüllt wurde.

Die Wait Methode erfordert, dass die Warte Thread eine Sperre haben, da dies der einzige Weg ist, kann es sicher sein, dass die Bedingung, es auf wartet nicht zwischen der Zeit verändern sie getestet hat und die Zeit der Code das führt Wait. Die Pulse Methode erfordert eine Sperre, da dies die einzige Möglichkeit ist, sicher zu sein, dass, wenn ein anderer Thread sich zur Ausführung einer Wait "committed", die Pulse nicht auftreten wird, bis der andere Thread dies tatsächlich tut. Beachten Sie, dass die Verwendung von Wait innerhalb eines Schlosses nicht garantiert, dass es korrekt verwendet wird, aber es gibt keine Möglichkeit, dass die Verwendung von Wait außerhalb einer Sperre möglicherweise korrekt ist.

Der Entwurf Wait/Pulse funktioniert tatsächlich einigermaßen gut, wenn beide Seiten zusammenarbeiten. Die größten Schwächen des Designs, IMHO, sind (1) es gibt keinen Mechanismus für einen Thread, um zu warten, bis einer von einer Anzahl von Objekten gepulst ist; (2) selbst wenn man ein Objekt "herunterfährt", so dass alle zukünftigen Warteschleifen sofort beendet werden sollten (wahrscheinlich durch Überprüfen eines Exit-Flags), kann nur sichergestellt werden, dass Wait, an die sich ein Thread gebunden hat, eine Pulse erhält ist, das Schloss zu erwerben, möglicherweise auf unbestimmte Zeit darauf wartend, dass es verfügbar wird.