oggetto Lock
Nota
Questo articolo è una specifica di funzionalità. La specifica funge da documento di progettazione per la funzionalità. Include le modifiche specifiche proposte, insieme alle informazioni necessarie durante la progettazione e lo sviluppo della funzionalità. Questi articoli vengono pubblicati fino a quando le modifiche specifiche proposte non vengono completate e incorporate nella specifica ECMA corrente.
Potrebbero verificarsi alcune discrepanze tra la specifica di funzionalità e l'implementazione completata. Tali differenze vengono registrate nelle pertinenti note del language design meeting (LDM).
Altre informazioni sul processo di adozione delle speclet di funzionalità nello standard del linguaggio C# sono disponibili nell'articolo sulle specifiche .
Problema del campione: https://github.com/dotnet/csharplang/issues/7104
Sommario
Caso speciale su come System.Threading.Lock
interagisce con la parola chiave lock
(chiamando in modo nascosto il relativo metodo EnterScope
).
Aggiungere avvisi di analisi statica per evitare un uso improprio accidentale del tipo, se possibile.
Motivazione
.NET 9 introduce un nuovo tipo di System.Threading.Lock
come alternativa migliore al blocco basato sul monitoraggio esistente.
La presenza della parola chiave lock
in C# potrebbe portare gli sviluppatori a pensare che possano usarla con questo nuovo tipo.
In tal modo, non si bloccherebbe secondo le semantiche di questo tipo, ma verrebbe trattato come qualsiasi altro oggetto e si utilizzerebbe un blocco basato sul monitor.
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
Progettazione dettagliata
La semantica dell'istruzione lock (§13.13) è stata modificata per trattare il tipo System.Threading.Lock
come un caso speciale.
Istruzione
lock
del modulolock (x) { ... }
- dove
x
è un'espressione di tipoSystem.Threading.Lock
, equivale esattamente a:eusing (x.EnterScope()) { ... }
System.Threading.Lock
devono avere la forma seguente:namespace System.Threading { public sealed class Lock { public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } }
- dove
x
è un'espressione di un reference_type, è esattamente equivalente a: [...]
Si noti che la forma potrebbe non essere completamente controllata (ad esempio, non ci saranno errori né avvisi se il tipo di Lock
non è sealed
), ma la funzionalità potrebbe non funzionare come previsto (ad esempio, non ci saranno avvisi durante la conversione di Lock
in un tipo derivato, poiché la funzionalità presuppone che non ci siano tipi derivati).
Inoltre, vengono aggiunti nuovi avvisi alle conversioni di riferimento implicite (§10.2.8) durante l'upcast del tipo di System.Threading.Lock
:
Le conversioni di riferimento implicite sono:
- Da qualsiasi reference_type a
object
edynamic
.
- Viene segnalato un avviso quando il reference_type è noto come
System.Threading.Lock
.- Da qualsiasi class_type
S
a qualsiasi class_typeT
, a condizione cheS
derivi daT
.
- Viene segnalato un avviso quando
S
è noto comeSystem.Threading.Lock
.- Da qualsiasi class_type
S
a qualsiasi interface_typeT
, a condizione cheS
implementiT
.
- Viene segnalato un avviso quando
S
è noto comeSystem.Threading.Lock
.- [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here
Si noti che questo avviso si verifica anche per conversioni esplicite equivalenti.
Il compilatore evita di segnalare l'avviso in alcuni casi quando l'istanza non può essere bloccata dopo la conversione in object
:
- quando la conversione è implicita e parte della chiamata di un operatore di uguaglianza di oggetti.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
// ...
Per uscire dall'avviso e forzare l'uso del blocco basato sul monitoraggio, è possibile usare
- i soliti mezzi di soppressione degli avvisi (
#pragma warning disable
) -
Monitor
API direttamente, - cast indiretto come
object AsObject<T>(T l) => (object)l;
.
Alternative
Supportare un modello generale che altri tipi possono usare anche per interagire con la parola chiave
lock
. Si tratta di un lavoro futuro che potrebbe essere implementato quandoref struct
può partecipare ai generics. Descritto in LDM 2023-12-04.Per evitare ambiguità tra il blocco basato sul monitoraggio esistente e il nuovo
Lock
(o modello in futuro), è possibile:- Introdurre una nuova sintassi anziché riutilizzare l'istruzione
lock
esistente. - Richiedere che i nuovi tipi di blocco siano
struct
(poiché illock
esistente non consente tipi valore). Potrebbero verificarsi problemi con i costruttori predefiniti e la copia se gli struct hanno inizializzazione differita.
- Introdurre una nuova sintassi anziché riutilizzare l'istruzione
Il codegen può essere sottoposto a protezione avanzata contro interruzioni del thread (che sono essi stessi obsoleti).
È possibile avvisare anche quando
Lock
viene passato come parametro di tipo, perché bloccare un parametro di tipo utilizza sempre il blocco basato sul monitor.M(new Lock()); // could warn here void M<T>(T x) // (specifying `where T : Lock` makes no difference) { lock (x) { } // because this uses Monitor }
Tuttavia, ciò causerebbe avvisi durante l'archiviazione di
Lock
in un elenco che è indesiderato:List<Lock> list = new(); list.Add(new Lock()); // would warn here
È possibile includere l'analisi statica per impedire l'uso di
System.Threading.Lock
inusing
conawait
. Ad esempio, è possibile generare un errore o un avviso per il codice comeusing (lockVar.EnterScope()) { await ... }
. Attualmente, questo non è necessario perchéLock.Scope
è unref struct
, in modo che il codice sia comunque illegale. Tuttavia, se permettessimo mairef struct
nei metodiasync
o modificassimoLock.Scope
per non essere unref struct
, questa analisi diventerebbe utile. È probabile che sia necessario prendere in considerazione anche per questo tutti i tipi di blocco corrispondenti al modello generale, se implementato in futuro. Anche se potrebbe essere necessario un meccanismo di rifiuto esplicito perché alcuni tipi di blocco potrebbero essere autorizzati a essere usati conawait
. In alternativa, questo può essere implementato come analizzatore fornito come parte del runtime.È possibile ridurre la restrizione che i tipi di valore non possono essere
lock
ed- per il nuovo tipo di
Lock
(necessario solo se la proposta dell'API lo ha modificato daclass
astruct
), - per il modello generale in cui qualsiasi tipo potrà partecipare una volta implementato in futuro.
- per il nuovo tipo di
È possibile consentire la nuova
lock
nei metodiasync
in cuiawait
non viene usato all'interno dellock
.- Attualmente, poiché
lock
viene ridotto ausing
con unref struct
come risorsa, viene generato un errore in fase di compilazione. La soluzione alternativa consiste nell'estrarre illock
in un metodo nonasync
separato. - Anziché usare il
ref struct Scope
, è possibile generare metodiLock.Enter
eLock.Exit
intry
/finally
. Tuttavia, il metodoExit
deve generare quando viene chiamato da un thread diverso daEnter
, pertanto contiene una ricerca di thread che viene evitata quando si usa ilScope
. - Sarebbe meglio consentire la compilazione di
using
in unref struct
nei metodiasync
se non ci sonoawait
all'interno del corpousing
.
- Attualmente, poiché
Riunioni di progettazione
-
LDM 2023-05-01: decisione iniziale di supportare un modello di
lock
- LDM 2023-10-16: incluso nel set di lavoro per .NET 9
-
LDM 2023-12-04: rifiutato il modello generale, accettata solo la gestione dei casi speciali per il tipo
Lock
+ in aggiunta avvisi di analisi statica
C# feature specifications