objeto Lock
Nota
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.
Pode haver algumas discrepâncias entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).
Você pode saber mais sobre o processo de adoção de especificações de funcionalidades no padrão da linguagem C# no artigo sobre as especificações .
Problema do especialista: https://github.com/dotnet/csharplang/issues/7104
Resumo
Caso especial de como System.Threading.Lock
interage com a palavra-chave lock
(chamando seu método EnterScope
nos bastidores).
Adicione avisos de análise estática para evitar o uso indevido acidental do tipo sempre que possível.
Motivação
O .NET 9 está introduzindo um novo tipo de System.Threading.Lock
como uma alternativa melhor ao bloqueio baseado em monitor existente.
A presença da palavra-chave lock
em C# pode levar os desenvolvedores a pensar que podem usá-la com esse novo tipo.
Fazer isso não causaria bloqueio de acordo com a semântica desse tipo, mas o trataria como qualquer outro objeto e usaria o bloqueio baseado em monitor.
namespace System.Threading
{
public sealed class Lock
{
public void Enter();
public void Exit();
public Scope EnterScope();
public ref struct Scope
{
public void Dispose();
}
}
}
Design detalhado
A semântica da instrução de bloqueio (§13.13) é alterada para tratar o tipo System.Threading.Lock
de forma especial.
Uma declaração
lock
do formuláriolock (x) { ... }
- em que
x
é uma expressão do tipoSystem.Threading.Lock
, é exatamente equivalente a:eusing (x.EnterScope()) { ... }
System.Threading.Lock
devem ter a seguinte forma:namespace System.Threading { public sealed class Lock { public Scope EnterScope(); public ref struct Scope { public void Dispose(); } } }
- em que
x
é uma expressão de um reference_type, é exatamente equivalente a: [...]
Observe que a forma pode não ser totalmente verificada (por exemplo, não haverá erros nem avisos se o tipo de Lock
não for sealed
), mas o recurso pode não funcionar conforme o esperado (por exemplo, não haverá avisos ao converter Lock
em um tipo derivado, pois o recurso pressupõe que não há tipos derivados).
Além disso, novos avisos são adicionados a conversões de referência implícitas (§10.2.8) ao fazer o upcast do tipo System.Threading.Lock
:
As conversões de referência implícitas são:
- De qualquer reference_type a
object
edynamic
.
- Um aviso é emitido quando o reference_type é conhecido como
System.Threading.Lock
.- De qualquer class_type
S
a qualquer class_typeT
, desde queS
seja derivado deT
.
- Um aviso é emitido quando
S
é conhecido por serSystem.Threading.Lock
.- De qualquer class_type
S
para qualquer interface_typeT
, desde queS
implementeT
.
- Um aviso é relatado quando
S
é conhecido comoSystem.Threading.Lock
.- [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here
Observe que esse aviso ocorre mesmo para conversões explícitas equivalentes.
O compilador evita relatar o aviso em alguns casos em que a instância não pode ser bloqueada após a conversão em object
:
- quando a conversão estiver implícita e parte de uma invocação de operador de igualdade de objetos.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
// ...
Para escapar do aviso e forçar o uso do bloqueio baseado em monitor, é possível usar
- os meios usuais de supressão de aviso (
#pragma warning disable
), -
Monitor
APIs diretamente, - conversão indireta como
object AsObject<T>(T l) => (object)l;
.
Alternativas
Dê suporte a um padrão geral que outros tipos também podem usar para interagir com a palavra-chave
lock
. Esse é um trabalho futuro que pode ser implementado quandoref struct
s puderem participar de genéricos. Discutido em LDM 2023-12-04.Para evitar ambiguidade entre o bloqueio baseado em monitor existente e o novo
Lock
(ou padrão no futuro), poderíamos:- Introduza uma nova sintaxe em vez de reutilize a instrução
lock
existente. - Exija que os novos tipos de bloqueio sejam
struct
s (já que olock
existente não permite tipos de valor). Pode haver problemas com construtores padrão e cópia se os structs tiverem inicialização lenta.
- Introduza uma nova sintaxe em vez de reutilize a instrução
O codegen pode ser protegido contra anulações de thread (em que são eles mesmos obsoletos).
Podemos avisar também quando
Lock
é passado como um parâmetro de tipo, pois o bloqueio em um parâmetro de tipo sempre usa o bloqueio baseado em 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 }
No entanto, isso causaria avisos ao armazenar
Lock
s em uma lista indesejável:List<Lock> list = new(); list.Add(new Lock()); // would warn here
Podemos incluir análise estática para impedir o uso de
System.Threading.Lock
emusing
s comawait
s. Ou seja, poderíamos emitir um erro ou um aviso para um código comousing (lockVar.EnterScope()) { await ... }
. Atualmente, isso não é necessário, poisLock.Scope
é umref struct
, de modo que o código seja ilegal de qualquer maneira. No entanto, se alguma vez permitirmosref struct
em métodosasync
ou mudássemosLock.Scope
para deixar de ser umref struct
, essa análise se tornaria benéfica. (Provavelmente, também precisaríamos considerar todos os tipos de bloqueio que correspondem ao padrão geral se implementados no futuro. Embora talvez seja necessário haver um mecanismo de exclusão, pois alguns tipos de bloqueio podem ter permissão para serem usados comawait
.) Alternativamente, isso pode ser implementado como um analisador enviado como parte do tempo de execução.Poderíamos relaxar a restrição de que os tipos de valor não podem ser
lock
ed- para o novo tipo de
Lock
(necessário somente se a proposta da API a alterou declass
parastruct
), - para o modelo geral em que qualquer tipo poderá participar quando for implementado no futuro.
- para o novo tipo de
Poderíamos permitir o novo
lock
em métodosasync
em queawait
não é usado dentro dolock
.- Atualmente, já que
lock
é transformado emusing
utilizandoref struct
como recurso, isso resulta em um erro de compilação. A solução alternativa é extrair olock
em um método nãoasync
separado. - Em vez de usar o
ref struct Scope
, poderíamos emitir métodosLock.Enter
eLock.Exit
emtry
/finally
. No entanto, o métodoExit
deve ser lançado quando é chamado a partir de um thread diferente deEnter
, portanto, ele contém uma pesquisa de thread que é evitada ao usar oScope
. - Seria melhor permitir a compilação de
using
em umref struct
nos métodosasync
, caso não hajaawait
dentro do corpo deusing
.
- Atualmente, já que
Reuniões de design
- LDM 2023-05-01: decisão inicial de dar suporte ao padrão
lock
- LDM 2023-10-16: classificado no conjunto de trabalho do .NET 9
- LDM 2023-12-04: rejeitou o padrão geral, aceitou apenas a caixa especial do tipo
Lock
+ adicionando avisos de análise estática
C# feature specifications