Ein Nebenläufigkeitsfehler meldet sich selten mit einer Fehlermeldung. Er zeigt sich als zwei Buchungen für denselben Termin, als Zähler, der nicht stimmt, oder als App, die plötzlich nicht mehr antwortet, ohne Ausnahme im Log und mit fast keiner Prozessorlast. Diese Lektion behandelt die drei häufigsten Muster, zeigt, wie Sie jedes davon auf Bestellung nachstellen, und wie Sie mit Werkzeugen sehen, wo die Threads stehen. Und sie zeigt die wichtigste Erfahrung dieses Gebiets: Die Reparatur ist fast nie „noch eine Sperre“, sondern ein anderes Design.
Drei Muster
Prüfen-dann-Handeln (check-then-act). Code prüft eine Bedingung und handelt danach: „Ist 10 Uhr frei? Dann buche ich.“ Zwischen Prüfen und Handeln liegt ein Augenblick, und in diesem Augenblick kann ein anderer Thread dieselbe Prüfung machen und dasselbe Ergebnis bekommen. Beide buchen. Jede einzelne Zeile ist richtig, nur die Folge ist es nicht.
Verlorene Updates (lost update). Zwei Threads lesen denselben Wert, ändern ihn, schreiben zurück; einer überschreibt den anderen. Das
count++ aus Lektion 3.11.4 ist der kleinste Fall, ein Kontakt, den zwei Personen gleichzeitig bearbeiten, der größte. In der Datenbank kennen Sie
das schon: Optimistische Nebenläufigkeit mit einer Versionsspalte und DbUpdateConcurrencyException aus K2 ist die Antwort darauf.
Verklemmung (deadlock). Zwei Threads warten aufeinander, jeder hält, was der andere braucht. Keiner kommt weiter, für immer. Zwei Formen kommen in .NET-Code fast immer vor:
- Sperren in verschiedener Reihenfolge. Thread A nimmt Sperre 1, dann 2; Thread B nimmt Sperre 2, dann 1. Haben beide ihre erste, wartet jeder auf die zweite des anderen.
- Synchrones Warten auf Asynchrones in einem Kontext mit einem Thread.
.Resultblockiert den einzigen Thread eines Kontexts, und die Fortsetzung, die das Ergebnis liefern würde, wartet darauf, auf genau diesem Thread laufen zu dürfen.