Does Monitor.Wait braucht Synchronisation?
-
05-10-2019 - |
Frage
Ich habe eine generische Erzeuger-Verbraucher-Warteschlange entwickelt, die in der folgenden Art und Weise von Monitor-Impulse:
die 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;
}
der Warteabschnitt erzeugt die folgende SynchronizationLockException: „-Objekt Synchronisationsverfahren wurde von einem unsynchronisierten Codeblock namens“ brauche ich, um es zu synchronisieren? Warum ? Ist es besser, Verwendung ManualResetEvents oder die Slim-Version von .NET 4.0?
Lösung
Ja, die aktuelle Thread Bedürfnisse „eigenen“ der Monitor, um entweder Wait
oder Pulse
zu nennen, wie dokumentiert. (So ??werden Sie zum Schloss für Pulse
brauchen auch.) Ich weiß nicht, die Details, warum es notwendig ist, aber es ist das gleiche in Java. Ich habe festgestellt, in der Regel würde ich das sowieso obwohl tun will, den anrufende Code sauber zu machen.
Beachten Sie, dass Wait
gibt den Monitor selbst, wartet dann auf die Pulse
, zurückkauft dann den Monitor vor der Rückkehr.
Wie für die Verwendung von ManualResetEvent
oder AutoResetEvent
statt -. Sie könnten, aber ich persönlich bevorzuge die Monitor
Methoden verwenden, wenn ich einige der anderen Merkmale der Wartegriffe (wie atomar warten auf jede / alle mehrere Griffe) benötigen
Andere Tipps
Aus der MSDN Beschreibung Monitor.Wait ():
Gibt die Sperre für ein Objekt und blockiert den aktuellen Thread, bis er die Sperre zurückkauft.
Die ‚die Sperre‘ Teil das Problem ist, wird das Objekt nicht gesperrt. Sie behandeln die _locker Objekt, als ob es ein Waithandle ist. Ihre eigene Verriegelung Design zu tun, die beweisbar korrekter ist, ist eine Form der schwarzen Magie, die unseren Medizinmann, Jeffrey Richter und Joe Duffy am besten noch übrig ist. 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 jedes praktischen Producer / Consumer-Szenario, das Sie an den Hersteller drosseln wollen, damit es nicht in die Warteschlange unbegrenzt füllen kann. Prüfen Duffy BoundedBuffer design für ein Beispiel. Wenn Sie sich leisten können zu .NET bewegen 4.0 dann wollen Sie auf jeden Fall die Vorteile seiner ConcurrentQueue Klasse nehmen, ist es viel mehr schwarze Magie mit geringem Overhead Schließ- und Spin-Warten hat.
Der richtige Weg zur Ansicht Monitor.Wait
und Monitor.Pulse
/ ist PulseAll
nicht als Mittel des Wartens Bereitstellung, sondern (für Wait
) als ein Mittel, um das System wissen zu lassen, dass der Code in einer Warteschleife, die erst verlassen können etwas von Interesse Veränderungen und (für Pulse
/ PulseAll
) als ein Mittel, um das System wissen zu lassen, dass Code nur etwas verändert hat, dass die Austrittsbedingung erfüllen einige andere Threads Warteschleife verursachen könnten. Man sollte alle Vorkommen von Wait
mit Sleep(0)
ersetzen können und immer noch Code richtig funktioniert haben (auch wenn viel weniger effizient, als Folge der CPU-Zeit wiederholt die Ausgaben Testbedingungen, die sich nicht verä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 sich der Zustand so, dass sie erfüllt ist.
-
Der Code in dem anderen Thread pulst die Sperre (die noch niemand auf wartet).
-
Der Code in der Warteschleife führt ein
Wait
seit seiner Bedingung erfüllt wurde nicht.
Die Wait
Methode erfordert, dass die Warte Thread eine Sperre haben, da dies der einzige Weg ist, kann es sicher sein, dass die Bedingung, auf sie wartet nicht zwischen der Zeit verändert sie getestet haben und die Zeit, den Code führt die Wait
. Die Pulse
Verfahren erfordert eine Sperre, weil das der einzige Weg ist, kann es sein, dass Sie sicher, wenn ein anderer Thread „begangen“ selbst eine Wait
der Durchführung hat, die Pulse
erst erfolgen, nachdem der andere Thread so tatsächlich der Fall ist. Beachten Sie, dass mit Wait
innerhalb einer Sperre garantiert nicht, dass es richtig verwendet wird ist, aber es gibt keine Möglichkeit, dass Wait
außerhalb eines Schlosses mit möglicherweise richtig sein könnte.
Das Wait
/ Pulse
Design funktioniert eigentlich recht gut, wenn beide Seiten zusammenarbeiten. Die größten Schwächen der Konstruktion, IMHO, sind (1) gibt es keinen Mechanismus für einen Thread, bis eine beliebige Anzahl von Objekten zu warten, wird gepulst; (2), auch wenn man „heruntergefahren“ ein Objekt, so dass alle zukünftigen Warteschleifen sofort verlassen sollte (wahrscheinlich durch eine Exit-Flag überprüft), der einzige Weg, um sicherzustellen, dass Wait
, an dem ein Gewinde hat sich verpflichtet, eine Pulse
zu bekommen ist die Sperre zu erwerben, möglicherweise auf unbestimmte Zeit für sie zu werden, zur Verfügung zu warten.