Bleibt ein gesperrtes Objekt gesperrt, wenn eine Ausnahme darin auftritt?

83

Wenn ich in der ac # threading-App ein Objekt sperren würde, sagen wir eine Warteschlange, und wenn eine Ausnahme auftritt, bleibt das Objekt gesperrt? Hier ist der Pseudocode:

int ii;
lock(MyQueue)
{
   MyClass LclClass = (MyClass)MyQueue.Dequeue();
   try
   {
      ii = int.parse(LclClass.SomeString);
   }
   catch
   {
     MessageBox.Show("Error parsing string");
   }
}

So wie ich es verstehe, wird Code nach dem Fang nicht ausgeführt - aber ich habe mich gefragt, ob die Sperre freigegeben wird.

Khadaji
quelle
1
Als letzter Gedanke (siehe Updates) - Sie sollten die Sperre wahrscheinlich nur für die Dauer der Warteschlange halten ... die Verarbeitung außerhalb der Sperre durchführen.
Marc Gravell
1
Code nach catch wird ausgeführt, weil die Ausnahme behandelt wird
cjk
Danke, ich muss das verpasst haben, sollte ich diese Frage löschen?
Vort3x
4
Es scheint, dass der Beispielcode für diese Frage nicht gut ist, aber die Frage ist ziemlich gültig.
SalvadorGomez
Von C # Designer - Lock & Exception
Jivan

Antworten:

87

Zuerst; Haben Sie TryParse in Betracht gezogen?

in li;
if(int.TryParse(LclClass.SomeString, out li)) {
    // li is now assigned
} else {
    // input string is dodgy
}

Die Sperre wird aus zwei Gründen aufgehoben. Erstens lockist im Wesentlichen:

Monitor.Enter(lockObj);
try {
  // ...
} finally {
    Monitor.Exit(lockObj);
}

Zweite; Sie fangen die innere Ausnahme ab und werfen sie nicht erneut aus, sodass sie locknie eine Ausnahme sieht. Natürlich halten Sie die Sperre für die Dauer einer MessageBox, was möglicherweise ein Problem darstellt.

Es wird also bis auf die tödlichsten katastrophalen, nicht behebbaren Ausnahmen veröffentlicht.

Marc Gravell
quelle
13
Ich bin mir der Tryparse bewusst, aber sie ist für meine Frage nicht wirklich relevant. Dies war ein einfacher Code, um die Frage zu erklären - kein echtes Problem in Bezug auf die Analyse. Bitte ersetzen Sie die Analyse durch einen Code, der den Fang erzwingt und es Ihnen bequem macht.
Khadaji
13
Wie wäre es mit einer neuen Ausnahme ("zur Veranschaulichung"); ;-p
Marc Gravell
1
Außer wenn ein TheadAbortExceptionzwischen dem Monitor.Enterund auftritt try: blogs.msdn.com/ericlippert/archive/2009/03/06/…
Igal Tabachnik
2
"fatale katastrophale nicht behebbare Ausnahmen" wie das Überqueren der Bäche.
Phil Cooper
98

Ich stelle fest, dass niemand in seinen Antworten auf diese alte Frage erwähnt hat, dass das Aufheben einer Sperre für eine Ausnahme eine unglaublich gefährliche Sache ist. Ja, Sperranweisungen in C # haben "endlich" Semantik. Wenn die Steuerung das Schloss normal oder abnormal verlässt, wird das Schloss freigegeben. Sie reden alle darüber, als wäre es eine gute Sache, aber es ist eine schlechte Sache! Wenn Sie einen gesperrten Bereich haben, der eine nicht behandelte Ausnahme auslöst, ist es richtig, den erkrankten Prozess unmittelbar vor der Zerstörung weiterer Benutzerdaten zu beenden , die Sperre nicht freizugeben und fortzufahren .

Betrachten Sie es so: Angenommen, Sie haben ein Badezimmer mit einem Schloss an der Tür und eine Reihe von Leuten, die draußen warten. Eine Bombe im Badezimmer geht hoch und tötet die Person dort. Ihre Frage lautet: "Wird in dieser Situation das Schloss automatisch entriegelt, damit die nächste Person ins Badezimmer gelangen kann?" Ja, es wird. Das ist keine gute Sache. Dort ist gerade eine Bombe hochgegangen und hat jemanden getötet! Die Wasserleitungen sind wahrscheinlich zerstört, das Haus ist nicht mehr baulich einwandfrei und es könnte eine weitere Bombe darin sein . Das Richtige ist , alle so schnell wie möglich rauszuholen und das ganze Haus abzureißen.

Ich meine, denken Sie darüber nach: Wenn Sie einen Codebereich gesperrt haben, um aus einer Datenstruktur zu lesen, ohne dass dieser in einem anderen Thread mutiert ist, und etwas in dieser Datenstruktur eine Ausnahme ausgelöst hat, stehen die Chancen gut, dass dies an der Datenstruktur liegt ist korrupt . Benutzerdaten sind jetzt durcheinander; Sie möchten zu diesem Zeitpunkt nicht versuchen, Benutzerdaten zu speichern , da Sie dann beschädigte Daten speichern. Beenden Sie einfach den Vorgang.

Wenn Sie einen Codebereich gesperrt haben, um eine Mutation durchzuführen, ohne dass ein anderer Thread gleichzeitig den Status liest, und die Mutation ausgelöst wird, ist dies jetzt sicher , wenn die Daten zuvor nicht beschädigt waren . Genau vor diesem Szenario soll das Schloss schützen . Jetzt erhält Code, der darauf wartet, diesen Status zu lesen, sofort Zugriff auf den beschädigten Status und stürzt wahrscheinlich selbst ab. Auch hier ist es richtig, den Prozess zu beenden.

Unabhängig davon, wie Sie es in Scheiben schneiden, ist eine Ausnahme innerhalb eines Schlosses eine schlechte Nachricht . Die richtige Frage lautet nicht: "Wird mein Schloss im Falle einer Ausnahme bereinigt?" Die richtige Frage lautet: "Wie stelle ich sicher, dass es innerhalb eines Schlosses keine Ausnahme gibt? Und wenn ja, wie strukturiere ich mein Programm so, dass Mutationen auf frühere gute Zustände zurückgesetzt werden?"

Eric Lippert
quelle
17
Dieses Problem ist ziemlich orthogonal zum Sperren von IMO. Wenn Sie eine erwartete Ausnahme erhalten, möchten Sie alles bereinigen, einschließlich Sperren. Und wenn Sie eine unerwartete Ausnahme erhalten, haben Sie ein Problem mit oder ohne Sperren.
CodesInChaos
9
Ich denke, dass die oben beschriebene Situation ein Generalismus ist. Manchmal beschreiben Ausnahmen katastrophale Ereignisse. Manchmal nicht. Jeder verwendet sie anders im Code. Es ist durchaus richtig, dass eine Ausnahme ein Signal für ein außergewöhnliches, aber nicht katastrophales Ereignis ist - vorausgesetzt, Ausnahmen = katastrophal, der Prozessbeendigungsfall ist zu spezifisch. Die Tatsache, dass es sich möglicherweise um ein katastrophales Ereignis handelt, ändert nichts an der Gültigkeit der Frage - der gleiche Gedankengang könnte dazu führen, dass Sie niemals eine Ausnahme behandeln. In diesem Fall wird der Prozess beendet ...
Gerasimos R
1
@ GerasimosR: In der Tat. Zwei Punkte zu betonen. Erstens sollten Ausnahmen als katastrophal angenommen werden, bis festgestellt wird, dass sie gutartig sind. Zweitens, wenn Sie eine harmlose Ausnahme aus einem gesperrten Bereich erhalten, ist der gesperrte Bereich wahrscheinlich schlecht gestaltet. Es macht wahrscheinlich zu viel Arbeit im Schloss.
Eric Lippert
1
Alles gebührender Respekt, aber es hört sich so an, als würden Sie Herrn Lippert sagen, dass das C # -Design des Schlossblocks, der unter der Haube eine endgültige Aussage verwendet, eine schlechte Sache ist. Stattdessen sollten wir jede App, die jemals eine Ausnahme in einer Sperranweisung hat, die nicht in der Sperre enthalten war, vollständig zum Absturz bringen. Vielleicht sagen Sie das nicht, aber wenn ja, bin ich wirklich froh, dass das Team den Weg gegangen ist, den sie gegangen sind, und nicht Ihren (aus puristischen Gründen extremistischen!) Weg. Allem Respekt.
Nicholas Petersen
2
Falls nicht klar ist, was ich unter nicht zusammensetzbar verstehe: Angenommen, wir haben eine Methode "transfer", die zwei Listen verwendet: s und d, sperrt s, sperrt d, entfernt ein Element aus s, fügt das Element zu d hinzu und entsperrt d, entsperrt s. Die Methode ist nur dann korrekt, wenn niemand gleichzeitig versucht, von Liste X nach Liste Y zu übertragen, während jemand anderes versucht, von Y nach X zu übertragen . Die Richtigkeit der Übertragungsmethode ermöglicht es Ihnen nicht, daraus eine korrekte Lösung für ein größeres Problem zu erstellen, da Sperren unsichere Mutationen des globalen Status sind. Um sicher zu "übertragen", müssen Sie über jede Sperre im Programm Bescheid wissen .
Eric Lippert
43

ja, das wird richtig freigegeben; lockfungiert als try/ finally, mit dem Monitor.Exit(myLock)in der endgültigen, also egal wie Sie beenden, wird es freigegeben. Als Randnotiz catch(... e) {throw e;}wird am besten vermieden, da dies die Stack-Trace beschädigt e; Es ist besser, es überhaupt nicht zu fangen , oder alternativ: Verwenden Sie es, throw;anstatt throw e;es erneut zu werfen.

Wenn Sie es wirklich wissen möchten, lautet eine Sperre in C # 4 / .NET 4:

{
    bool haveLock = false;
    try {
       Monitor.Enter(myLock, ref haveLock);
    } finally {
       if(haveLock) Monitor.Exit(myLock);
    }
} 
Marc Gravell
quelle
14

"Eine lock-Anweisung wird zu einem Aufruf von Monitor.Enter und anschließend zu einem try ... finally-Block kompiliert. Im finally-Block wird Monitor.Exit aufgerufen.

Die JIT-Codegenerierung für x86 und x64 stellt sicher, dass zwischen einem Monitor.Enter-Aufruf und einem unmittelbar darauf folgenden try-Block kein Thread-Abbruch auftreten kann. "

Entnommen aus: Diese Seite

GH.
quelle
1
Es gibt mindestens einen Fall, in dem dies nicht zutrifft: Thread-Abbruch im Debug-Modus in .net-Versionen vor 4. Der Grund dafür ist, dass der C # -Compiler einen NOP zwischen dem Monitor.Enterund dem einfügt try, sodass die Bedingung "unmittelbar folgt" des JIT wird verletzt.
CodesInChaos
6

Ihr Schloss wird ordnungsgemäß freigegeben. A lockverhält sich so:

try {
    Monitor.Enter(myLock);
    // ...
} finally {
    Monitor.Exit(myLock);
}

Und finallyBlöcke werden garantiert ausgeführt, egal wie Sie den tryBlock verlassen .

Ry-
quelle
Eigentlich kein wird Code „garantiert“ auszuführen (man könnte das Netzkabel ziehen, zum Beispiel), und das ist nicht ganz , was ein Schloss aussieht wie in 4.0 - siehe hier
Marc GRA
1
@MarcGravell: Ich habe darüber nachgedacht, zwei Fußnoten zu genau diesen zwei Punkten zu setzen. Und dann dachte ich, es würde nicht viel
ausmachen
1
@MarcGravel: Ich denke, jeder geht davon aus, dass man immer nicht über eine "Pull the Plug" -Situation spricht, da ein Programmierer keine Kontrolle darüber hat :)
Vort3x
5

Nur um Marc's exzellenter Antwort ein wenig hinzuzufügen.

Situationen wie diese sind der eigentliche Grund für die Existenz des lockSchlüsselworts. Es hilft Entwicklern sicherzustellen, dass die Sperre im finallyBlock freigegeben wird .

Wenn Sie gezwungen sind, Monitor.Enter/ Exitz. B. zur Unterstützung eines Timeouts zu verwenden, müssen Sie sicherstellen, dass der Aufruf an Monitor.Exitim finallyBlock erfolgt, um im Falle einer Ausnahme die ordnungsgemäße Freigabe der Sperre sicherzustellen.

Brian Rasmussen
quelle