Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
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
lockdo formuláriolock (x) { ... }
- em que
xé uma expressão do tipoSystem.Threading.Lock, é precisamente equivalente a:eusing (x.EnterScope()) { ... }System.Threading.Lockdevem 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
objectedynamic.
- Um aviso é gerado quando é sabido que o reference_type está
System.Threading.Lock.- De qualquer class_type
Sa qualquer class_typeT, desde queSderive deT.
- Um aviso é emitido quando se sabe que
SSystem.Threading.Lock.- De qualquer class_type
Spara qualquer interface_typeT, desde queSimplementeT.
- Um aviso é emitido quando se sabe que
SSystem.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), -
MonitorAPIs 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 structpuder 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
lockexistente. - Exija que os novos tipos de bloqueio sejam
structs (uma vez que olockexistente 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
Locks em uma lista que é indesejável:List<Lock> list = new(); list.Add(new Lock()); // would warn herePoderíamos incluir a análise estática para evitar o uso de
System.Threading.Lockemusings comawaits. 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 structs em métodosasyncou mudássemosLock.Scopepara 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 declassparastruct), - 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
locknosasyncmétodos ondeawaitnão é utilizado dentro dolock.- Atualmente, como
locké reduzido parausingcom umref structcomo recurso, isso resulta em um erro em tempo de compilação. A solução alternativa é extrair olockem um método nãoasyncseparado. - Em vez de usar o
ref struct Scope, poderíamos emitir os métodosLock.EntereLock.Exitemtry/finally. No entanto, o métodoExitdeve 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
usingem umref structem métodosasyncse não houverawaitdentro 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