Lock
-Objekt
Anmerkung
Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.
Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den entsprechenden Hinweisen zum Language Design Meeting (LDM) erfasst.
Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.
Champion Issue: https://github.com/dotnet/csharplang/issues/7104
Zusammenfassung
Legen Sie einen Sonderfall fest, wie System.Threading.Lock
mit dem Schlüsselwort lock
interagiert (indem Sie dessen Methode EnterScope
unter der Haube aufrufen).
Fügen Sie statische Analysewarnungen hinzu, um versehentlichen Missbrauch des Typs nach Möglichkeit zu verhindern.
Motivation
.NET 9 führt einen neuen System.Threading.Lock
Typ als bessere Alternative zur vorhandenen monitorbasierten Sperre ein.
Wenn das Schlüsselwort lock
in C# vorhanden ist, können Entwickler davon überzeugt sein, dass sie es mit diesem neuen Typ verwenden können.
Dies würde nicht gemäß der Semantik dieses Typs sperren, sondern ihn wie jedes andere Objekt behandeln und monitorbasiertes Sperren verwenden.
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
Detailentwurf
Die Semantik der Lock-Anweisung (§13.13) wird geändert, um den System.Threading.Lock
-Typ als Spezialfall zu behandeln:
Eine
lock
-Anweisung der Formlock (x) { ... }
- wobei
x
ein Ausdruck vom TypSystem.Threading.Lock
ist, entspricht genau dem:undusing (x.EnterScope()) { ... }
System.Threading.Lock
müssen die folgende Form aufweisen:namespace System.Threading { public sealed class Lock { public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } }
- wobei
x
ein Ausdruck eines reference_type ist, ist genau äquivalent zu: [...]
Beachten Sie, dass das Shape möglicherweise nicht vollständig überprüft wird (z. B. gibt es keine Fehler oder Warnungen, wenn der Lock
Typ nicht sealed
ist), aber das Feature funktioniert möglicherweise nicht wie erwartet (z. B. gibt es keine Warnungen beim Konvertieren von Lock
in einen abgeleiteten Typ, da das Feature davon ausgeht, dass keine abgeleiteten Typen vorhanden sind).
Außerdem werden neue Warnungen zu impliziten Konversionen von Referenzen (§10.2.8) hinzugefügt, wenn der Typ System.Threading.Lock
upcasted wird:
Die impliziten Verweiskonvertierungen sind:
- Von jedem reference_type nach
object
unddynamic
.
- Eine Warnung wird geliefert, wenn der reference_type als
System.Threading.Lock
bekannt ist.- Von jedem class_type
S
bis zu jeder class_typeT
, vorausgesetzt,S
wird vonT
abgeleitet.
- Eine Warnung wird gemeldet, wenn bekannt ist, dass
S
System.Threading.Lock
ist.- Von einem beliebigen class_type
S
zu einem beliebigen interface_typeT
, sofernS
T
implementiert.
- Eine Warnung wird geliefert, wenn
S
alsSystem.Threading.Lock
bekannt ist.- [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here
Beachten Sie, dass diese Warnung auch bei entsprechenden expliziten Konvertierungen auftritt.
Der Compiler vermeidet die Meldung der Warnung in einigen Fällen, wenn die Instanz nach der Konvertierung in object
nicht gesperrt werden kann:
- wenn die Konvertierung implizit und Teil eines Aufrufs eines Objektgleichheitsoperators ist.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
// ...
Um die Warnung zu umgehen und die Verwendung von monitorbasiertem Sperren zu erzwingen, können Sie
- die üblichen Mittel zur Unterdrückung von Warnungen verwenden (
#pragma warning disable
), -
Monitor
APIs direkt verwenden, - indirektes Casting wie
object AsObject<T>(T l) => (object)l;
.
Alternativen
Unterstützen Sie ein allgemeines Muster, das andere Typen auch für die Interaktion mit dem schlüsselwort
lock
verwenden können. Dies ist eine zukünftige Arbeit, die implementiert werden könnte, wennref struct
s an Generics teilnehmen können. Diskutiert in LDM 2023-12-04.Um Mehrdeutigkeit zwischen der vorhandenen monitorbasierten Sperre und dem neuen
Lock
(oder zukünftigen Mustern) zu vermeiden, könnten wir:- Führen Sie eine neue Syntax ein, anstatt die vorhandene
lock
-Anweisung erneut zu verwenden. - Verlangen Sie, dass die neuen Lock-Typen
struct
s sind (da das bestehendelock
Wert-Typen nicht zulässt). Es können Probleme mit Standardkonstruktoren und Kopieren auftreten, wenn die Strukturen eine faule Initialisierung aufweisen.
- Führen Sie eine neue Syntax ein, anstatt die vorhandene
Der Codegen könnte gegen Threadabbrüche gehärtet werden (die selbst veraltet sind).
Wir könnten auch warnen, wenn
Lock
als Typparameter übergeben wird, da beim Sperren eines Typparameters immer monitorbasierte Sperren verwendet werden:M(new Lock()); // could warn here void M<T>(T x) // (specifying `where T : Lock` makes no difference) { lock (x) { } // because this uses Monitor }
Dies würde jedoch Warnungen verursachen, wenn
Lock
s in einer Liste gespeichert werden, die nicht erwünscht ist:List<Lock> list = new(); list.Add(new Lock()); // would warn here
Wir könnten eine statische Analyse einbauen, um die Verwendung von
System.Threading.Lock
inusing
s mitawait
s zu verhindern. D.h., wir könnten entweder einen Fehler oder eine Warnung für Code wieusing (lockVar.EnterScope()) { await ... }
ausgeben. Derzeit ist dies nicht erforderlich, daLock.Scope
einref struct
ist, damit der Code trotzdem illegal ist. Sollten wir jedoch jemalsref struct
s inasync
-Methoden zulassen oderLock.Scope
so ändern, dass es keinref struct
ist, würde diese Analyse von Nutzen sein. (Wir müssten wahrscheinlich auch alle Lock-Typen berücksichtigen, die dem allgemeinen Muster entsprechen, falls dies in Zukunft implementiert wird. Allerdings müsste es einen Opt-Out-Mechanismus geben, da einige Lock-Typen die Möglichkeit bieten könnten, mitawait
verwendet zu werden.) Alternativ könnte dies als ein Analysator implementiert werden, der als Element der Runtime ausgeliefert wird.Wir könnten die Einschränkung lockern, dass Wertetypen nicht
lock
ed werden können.- für den neuen
Lock
-Typ (nur erforderlich, wenn der API-Vorschlag den Typ vonclass
instruct
geändert hat), - für das allgemeine Muster, an dem jeder Typ teilnehmen kann, wenn er in Zukunft implementiert wird.
- für den neuen
Wir konnten die neue
lock
inasync
Methoden zulassen, bei denenawait
nicht innerhalb derlock
verwendet wird.- Aktuell führt dies zu einem Kompilierfehler, da
lock
zuusing
mit einerref struct
als Ressource herabgesetzt wird. Die Abhilfe besteht darin, daslock
in eine separate Nicht-async
-Methode zu extrahieren. - Anstatt die
ref struct Scope
zu verwenden, könnten wirLock.Enter
undLock.Exit
Methoden intry
/finally
ausgeben. DieExit
-Methode muss jedoch ausgelöst werden, wenn sie von einem anderen Thread alsEnter
aufgerufen wird, daher enthält sie eine Threadsuche, die bei Verwendung derScope
vermieden wird. - Am besten wäre es, die Kompilierung von
using
auf einemref struct
inasync
Methoden zuzulassen, wenn es keinawait
innerhalb desusing
Bodys gibt.
- Aktuell führt dies zu einem Kompilierfehler, da
Design-Besprechungen
- LDM 2023-05-01: Erste Entscheidung, ein
lock
-Muster zu unterstützen - LDM 2023-10-16: In das Arbeits-Set für .NET 9 festgelegt
- LDM 2023-12-04: verwarf das allgemeine Muster, akzeptierte nur die Sonderkodierung des
Lock
Typs + Hinzufügen von Warnungen zur statischen Analyse
C# feature specifications