Lock
objeto
Observação
Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ele inclui mudanças de especificação propostas, juntamente com as informações necessárias durante o design e desenvolvimento do recurso. Estes artigos são publicados até que as alterações de especificações 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 Language Design Meeting (LDM).
Você pode saber mais sobre o processo de adoção de especificações de recursos no padrão de linguagem C# no artigo sobre as especificações .
Questão campeã: https://github.com/dotnet/csharplang/issues/7104
Resumo
Especificar como System.Threading.Lock
interage com a palavra-chave lock
(chamando o seu método EnterScope
em segundo plano).
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 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 bloquearia segundo a semântica desse tipo, mas sim tratá-lo-ia 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();
}
}
}
Projeto detalhado
A semântica da instrução de bloqueio (§13.13) é alterada para tratar especificamente o tipo System.Threading.Lock
.
Uma declaração
lock
do formuláriolock (x) { ... }
- em que
x
é uma expressão do tipoSystem.Threading.Lock
, é precisamente 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(); } } }
- onde
x
é uma expressão de um reference_type, é precisamente equivalente a: [...]
Observe que a forma pode não estar totalmente verificada (por exemplo, não haverá erros nem avisos se o tipo Lock
não for sealed
), mas o recurso pode não funcionar como esperado (por exemplo, não haverá avisos ao converter Lock
em um tipo derivado, já que o recurso assume que não há tipos derivados).
Além disso, novos avisos são adicionados às conversões de referência implícitas (§10.2.8) ao atualizar o tipo de System.Threading.Lock
:
As conversões de referência implícitas são:
- De qualquer tipo de referência a
object
edynamic
.
- Um aviso é gerado quando é sabido que o reference_type está
System.Threading.Lock
.- De qualquer class_type
S
a qualquer class_typeT
, desde queS
derive deT
.
- Um aviso é emitido quando se sabe que
S
System.Threading.Lock
.- De qualquer class_type
S
para qualquer interface_typeT
, desde queS
implementeT
.
- Um aviso é comunicado quando se sabe que
S
estáSystem.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 quando a instância não pode ser bloqueada após a conversão para object
:
- quando a conversão é implícita e parte de uma invocação do operador de igualdade de objeto.
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, pode-se usar
- os meios habituais de supressão dos avisos (
#pragma warning disable
), -
Monitor
APIs diretamente, - casting indireto como
object AsObject<T>(T l) => (object)l;
.
Alternativas
Suporte a um padrão geral que outros tipos também podem usar para interagir com a palavra-chave
lock
. Este é um trabalho futuro que poderá ser implementado quandoref struct
puder participar em 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 reutilizar a instrução
lock
existente. - Exija que os novos tipos de bloqueio sejam
struct
s (uma vez que olock
existente não permite tipos de valor). Pode haver problemas com construtores padrão e operações de cópia se as structs tiverem inicialização lenta.
- Introduza uma nova sintaxe em vez de reutilizar a instrução
O codegen pode ser fortalecido contra interrupções de threads (que estão obsoletos).
Poderíamos avisar também quando
Lock
é passado como um parâmetro de tipo, porque o bloqueamento em um parâmetro de tipo sempre usa bloqueamento 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 que é indesejável:List<Lock> list = new(); list.Add(new Lock()); // would warn here
Poderíamos incluir a análise estática para evitar o uso de
System.Threading.Lock
emusing
s comawait
s. Ou seja, podemos emitir um erro ou um aviso para um código comousing (lockVar.EnterScope()) { await ... }
. Atualmente, isso não é necessário, uma vez queLock.Scope
é umref struct
, de modo que o código é ilegal de qualquer maneira. No entanto, se alguma vez permitíssemosref struct
s em métodosasync
ou mudássemosLock.Scope
para não ser umref struct
, esta análise tornar-se-ia benéfica. (Também provavelmente precisaríamos considerar para isso todos os tipos de bloqueio correspondentes ao padrão geral, se implementados no futuro. Embora possa ser necessário um mecanismo de exclusão, pois alguns tipos de bloqueio podem ser permitidos para serem usados comawait
.) Como alternativa, isso pode ser implementado como um analisador enviado como parte do tempo de execução.Poderíamos flexibilizar a restrição de que os tipos de valores não podem ser
lock
- para o novo tipo de
Lock
(necessário apenas se a proposta da API o alterou declass
parastruct
), - para o padrão geral no qual qualquer tipo pode participar quando implementado no futuro.
- para o novo tipo de
Poderíamos permitir a nova
lock
nosasync
métodos ondeawait
não é utilizado dentro dolock
.- Atualmente, como
lock
é reduzido parausing
com umref struct
como recurso, isso resulta em um erro em tempo 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 os métodosLock.Enter
eLock.Exit
emtry
/finally
. No entanto, o métodoExit
deve ser lançado quando é chamado de um thread diferente deEnter
, portanto, ele contém uma pesquisa de thread que é evitada ao usar oScope
. - O melhor seria permitir a compilação de
using
em umref struct
em métodosasync
se não houverawait
dentro do corpousing
.
- Atualmente, como
Reuniões de design
-
LDM 2023-05-01: decisão inicial de apoiar um padrão
lock
- LDM 2023-10-16: incluído no conjunto de trabalho para .NET 9
-
LDM 2023-12-04: rejeitou o padrão geral, aceitou apenas a caixa especial do tipo
Lock
+ adição de avisos de análise estática
C# feature specifications