obiekt Lock
Notatka
Ten artykuł jest specyfikacją funkcji. Specyfikacja służy jako dokument projektowy dla funkcji. Zawiera proponowane zmiany specyfikacji wraz z informacjami wymaganymi podczas projektowania i opracowywania funkcji. Te artykuły są publikowane do momentu sfinalizowania proponowanych zmian specyfikacji i włączenia ich do obecnej specyfikacji ECMA.
Mogą wystąpić pewne rozbieżności między specyfikacją funkcji a ukończoną implementacją. Te różnice są odnotowane w odpowiednich notatkach ze spotkania dotyczącego projektowania języka (LDM) .
Więcej informacji na temat procesu wdrażania specyfikacji funkcji można znaleźć w standardzie języka C# w artykule dotyczącym specyfikacji .
Problem z mistrzem: https://github.com/dotnet/csharplang/issues/7104
Streszczenie
Szczególny przypadek interakcji System.Threading.Lock
ze słowem kluczowym lock
(wywoływanie metody EnterScope
w tle).
Dodaj ostrzeżenia analizy statycznej, aby zapobiec przypadkowemu niewłaściwemu użyciu typu, jeśli jest to możliwe.
Motywacja
Platforma .NET 9 wprowadza nowy typ System.Threading.Lock
jako lepszą alternatywę dla istniejących blokad opartych na monitorach.
Obecność słowa kluczowego lock
w języku C# może prowadzić deweloperów do myślenia, że mogą używać go z tym nowym typem.
W ten sposób nie zablokowałby się zgodnie z semantyką tego typu, ale zamiast tego traktowałby go jak każdy inny obiekt i używałby blokady opartej na monitorze.
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
Szczegółowy projekt
Semantyka instrukcji blokady (§13.13) zmienia się, aby traktować typ System.Threading.Lock
jako przypadek szczególny.
Oświadczenie
lock
w formielock (x) { ... }
- gdzie
x
jest wyrażeniem typuSystem.Threading.Lock
, jest dokładnie równoważne:iusing (x.EnterScope()) { ... }
System.Threading.Lock
muszą mieć następujący kształt:namespace System.Threading { public sealed class Lock { public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } }
- gdzie
x
jest wyrażeniem reference_type, jest dokładnie równoważne: [...]
Należy pamiętać, że kształt może nie zostać w pełni sprawdzony (np. nie będzie żadnych błędów ani ostrzeżeń, jeśli typ Lock
nie jest sealed
), ale funkcja może nie działać zgodnie z oczekiwaniami (np. podczas konwertowania Lock
na typ pochodny, ponieważ funkcja zakłada, że nie ma żadnych typów pochodnych).
Ponadto nowe ostrzeżenia są dodawane do niejawnych konwersji odwołań (§10.2.8) podczas podnoszenia typu System.Threading.Lock
.
Konwersje odwołań niejawnych to:
- Z dowolnego reference_type do
object
idynamic
.
- Ostrzeżenie jest zgłaszane, gdy reference_type jest znany jako
System.Threading.Lock
.- Od dowolnego class_type
S
do dowolnego class_typeT
, pod warunkiem, żeS
pochodzi zT
.
- ostrzeżenie jest zgłaszane, gdy
S
jest znany jakoSystem.Threading.Lock
.- Z dowolnego class_type
S
do dowolnego interface_typeT
, pod warunkiem, żeS
implementujeT
.
- ostrzeżenie jest zgłaszane, gdy
S
jest znany jakoSystem.Threading.Lock
.- [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here
Należy pamiętać, że to ostrzeżenie pojawi się nawet przy równoważnych jawnych konwersjach.
Kompilator unika raportowania ostrzeżenia w niektórych przypadkach, gdy nie można zablokować wystąpienia po przekonwertowaniu na object
:
- gdy konwersja jest niejawna i stanowi część wywołania operatora porównania obiektu.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
// ...
Aby uciec przed ostrzeżeniem i wymusić użycie blokady opartej na monitorze, można użyć
- zwykłe środki tłumienia ostrzeżeń (
#pragma warning disable
), - API
Monitor
bezpośrednio, - rzutowanie pośrednie, takie jak np.
object AsObject<T>(T l) => (object)l;
.
Alternatywy
Obsługa ogólnego wzorca, którego mogą również używać inne typy do interakcji ze słowem kluczowym
lock
. Jest to przyszły projekt, który może zostać wdrożony, gdyref struct
będzie mógł brać udział w uogólnieniach. Omówiono w LDM 2023-12-04.Aby uniknąć niejednoznaczności między istniejącym blokowaniem opartym na monitorze a nowym
Lock
(lub wzorcem w przyszłości), możemy:- Wprowadzenie nowej składni zamiast ponownego użycia istniejącej instrukcji
lock
. - Należy wymagać, aby nowe typy blokad były
struct
, ponieważ istniejącelock
nie zezwalają na typy wartości. Mogą wystąpić problemy z konstruktorami domyślnymi i kopiowaniem, jeśli struktury mają opóźnioną inicjację.
- Wprowadzenie nowej składni zamiast ponownego użycia istniejącej instrukcji
Generowanie kodu może być wzmocnione przeciwko przerwaniom wątków (które same w sobie są przestarzałe).
Możemy również ostrzegać, gdy
Lock
jest przekazywany jako parametr typu, ponieważ blokowanie parametru typu zawsze używa blokady opartej na monitorze:M(new Lock()); // could warn here void M<T>(T x) // (specifying `where T : Lock` makes no difference) { lock (x) { } // because this uses Monitor }
Spowoduje to jednak ostrzeżenia podczas przechowywania
Lock
s na liście, co jest niepożądane.List<Lock> list = new(); list.Add(new Lock()); // would warn here
Możemy uwzględnić analizę statyczną, aby zapobiec użyciu
System.Threading.Lock
wusing
s zawait
s. Tj. możemy wyemitować błąd lub ostrzeżenie dotyczące kodu, takiego jakusing (lockVar.EnterScope()) { await ... }
. Obecnie nie jest to potrzebne, ponieważLock.Scope
jestref struct
, więc kod jest mimo to nielegalny. Jeśli jednak kiedykolwiek pozwolilibyśmyref struct
w metodachasync
lub zmienilibyśmyLock.Scope
, aby nie byłoref struct
, ta analiza stałaby się użyteczna. (Proponujemy również rozważyć wszystkie typy blokad zgodne z ogólnym wzorcem, jeśli zostaną wdrożone w przyszłości. Może być jednak konieczny mechanizm wyłączenia, ponieważ niektóre typy blokad mogą być używane zawait
. Alternatywnie, można to wdrożyć jako analizator zapewniany w ramach środowiska uruchomieniowego.)Możemy złagodzić ograniczenie, że typy wartości nie mogą być
lock
ed- dla nowego typu
Lock
(wymagane tylko wtedy, gdy propozycja interfejsu API zmieniła go zclass
nastruct
), - dla ogólnego wzorca, w którym dowolny typ może uczestniczyć w przypadku implementacji w przyszłości.
- dla nowego typu
Możemy zezwolić na nowe
lock
w metodachasync
, jeśliawait
nie jest używana wewnątrzlock
.- Obecnie, ponieważ
lock
jest obniżony dousing
przy użyciuref struct
jako zasobu, powoduje to błąd czasu kompilacji. Obejściem jest wyodrębnienielock
do oddzielnej metody innej niżasync
. - Zamiast używać
ref struct Scope
, możemy emitować metodyLock.Enter
iLock.Exit
wtry
/finally
. Jednak metodaExit
musi zostać wyrzucona, gdy jest wywoływana z innego wątku niżEnter
, dlatego zawiera wyszukiwanie wątków, którego unika się podczas korzystania zScope
. - Najlepiej byłoby zezwolić na kompilację
using
naref struct
w metodachasync
, jeśli nie maawait
wewnątrz ciałausing
.
- Obecnie, ponieważ
Spotkania projektowe
-
LDM 2023-05-01: początkowa decyzja o wsparciu wzorca
lock
- LDM 2023-10-16: przeprowadzono triage do zestawu roboczego dla platformy .NET 9
-
LDM 2023-12-04: odrzucił ogólny wzorzec, zaakceptował tylko specjalne traktowanie typu
Lock
i dodanie ostrzeżeń analizy statycznej
C# feature specifications