Partager via


Objet Lock

Remarque

Cet article est une spécification de fonctionnalité. La spécification sert de document de conception pour la fonctionnalité. Il inclut les modifications de spécification proposées, ainsi que les informations nécessaires pendant la conception et le développement de la fonctionnalité. Ces articles sont publiés jusqu’à ce que les modifications de spécification proposées soient finalisées et incorporées dans la spécification ECMA actuelle.

Il peut y avoir des différences entre la spécification de la fonctionnalité et l’implémentation terminée. Ces différences sont consignées dans les notes pertinentes de la réunion de conception linguistique (LDM).

Vous pouvez en savoir plus sur le processus d’adoption des speclets de fonctionnalités dans la norme de langage C# dans l’article sur les spécifications .

Problème de champion : https://github.com/dotnet/csharplang/issues/7104

Résumé

Cas particulier où System.Threading.Lock interagit avec le mot clé lock (appel de sa méthode EnterScope sous le capot). Ajoutez des avertissements d’analyse statique pour éviter toute utilisation accidentelle du type, le cas échéant.

Motivation

.NET 9 introduit un nouveau type de System.Threading.Lock comme meilleure alternative au verrouillage basé sur le moniteur existant. La présence du mot clé lock en C# peut amener les développeurs à penser qu’ils peuvent l’utiliser avec ce nouveau type. Cela ne verrouillerait pas en fonction de la sémantique de ce type, mais le traiterait plutôt comme n’importe quel autre objet et utiliserait le verrouillage basé sur le moniteur.

namespace System.Threading
{
    public sealed class Lock
    {
        public void Enter();
        public void Exit();
        public Scope EnterScope();
    
        public ref struct Scope
        {
            public void Dispose();
        }
    }
}

Conception détaillée

La sémantique de l’instruction lock (§13.13) est modifiée pour traiter de façon spéciale le type System.Threading.Lock :

Une instruction lock du formulaire lock (x) { ... }

  1. x est une expression de type System.Threading.Lock, équivaut précisément à :
    using (x.EnterScope())
    {
        ...
    }
    
    et System.Threading.Lock doivent avoir la forme suivante :
    namespace System.Threading
    {
        public sealed class Lock
        {
            public Scope EnterScope();
    
            public ref struct Scope
            {
                public void Dispose();
            }
        }
    }
    
  2. x est une expression d’un reference_type, est précisément équivalente à : [...]

Notez que la forme peut ne pas être entièrement vérifiée (par exemple, il n’y aura pas d’erreurs ni d’avertissements si le type Lock n’est pas sealed), mais que la fonctionnalité peut ne pas fonctionner comme prévu (par exemple, il n’y aura pas d’avertissements lors de la conversion de Lock en type dérivé, car la fonctionnalité suppose qu’il n’existe aucun type dérivé).

De plus, de nouveaux avertissements sont ajoutés aux conversions de référence implicites (§10.2.8) lors du surclassement du type System.Threading.Lock :

Les conversions de référence implicites sont les suivantes :

  • De n’importe quel reference_type à object et dynamic.
    • Un avertissement est signalé lorsque le reference_type est connu pour être System.Threading.Lock.
  • De n’importe quel class_typeS à n’importe quel class_typeT, à condition que S soit dérivé de T.
    • Un avertissement est signalé lorsque S est connu pour être System.Threading.Lock.
  • De n’importe quel class_typeS à n’importe quel interface_typeT, à condition que S implémente T.
    • Un avertissement est signalé lorsque S est connu pour être System.Threading.Lock.
  • [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here

Notez que cet avertissement se produit même pour les conversions explicites équivalentes.

Le compilateur évite de signaler l’avertissement dans certains cas lorsque l’instance ne peut pas être verrouillée après la conversion en object:

  • lorsque la conversion est implicite et qu’elle fait partie d’un appel d’opérateur d’égalité d’objet.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
    // ...

Pour échapper à l’avertissement et forcer l’utilisation du verrouillage basé sur le moniteur, il est possible d’utiliser

  • les moyens habituels de suppression des avertissements (#pragma warning disable),
  • API Monitor directement,
  • diffusion indirecte comme object AsObject<T>(T l) => (object)l;.

Alternatives

  • Soutenir un modèle général que d'autres types peuvent également utiliser pour interagir avec le mot clé lock. Il s’agit d’un projet futur qui pourrait être implémenté lorsque des ref struct peuvent participer à des génériques. Abordé dans LDM 2023-12-04.

  • Pour éviter toute ambiguïté entre le verrouillage basé sur le moniteur existant et la nouvelle Lock (ou modèle à l’avenir), nous pourrions :

    • Introduisez une nouvelle syntaxe au lieu de réutiliser l’instruction lock existante.
    • Exiger que les nouveaux types de verrous soient structs (étant donné que le lock existant interdit les types valeur). Il peut y avoir des problèmes avec les constructeurs par défaut et la copie si les structures utilisent une initialisation différée.
  • Le générateur de code pourrait être renforcé contre les abandons de thread (qui sont eux-mêmes obsolètes).

  • Nous pouvons également avertir quand Lock est passé en tant que paramètre de type, car le verrouillage sur un paramètre de type utilise toujours le verrouillage basé sur le moniteur :

    M(new Lock()); // could warn here
    
    void M<T>(T x) // (specifying `where T : Lock` makes no difference)
    {
        lock (x) { } // because this uses Monitor
    }
    

    Toutefois, cela entraînerait des avertissements lors du stockage de Locks dans une liste qui n’est pas souhaitable :

    List<Lock> list = new();
    list.Add(new Lock()); // would warn here
    
  • Nous pourrions inclure une analyse statique pour empêcher l’utilisation de System.Threading.Lock dans usings avec awaits. Par exemple, nous pourrions émettre une erreur ou un avertissement pour le code comme using (lockVar.EnterScope()) { await ... }. Actuellement, cela n’est pas nécessaire, car Lock.Scope est un ref struct, de sorte que le code est illégal de toute façon. Toutefois, si jamais nous permettions des ref struct dans les méthodes async ou changions Lock.Scope pour qu'il ne soit pas un ref struct, cette analyse deviendrait bénéfique. (Nous aurions également besoin de prendre en compte tous les types de verrous correspondant au modèle général s’ils sont implémentés à l’avenir. Bien qu’il puisse être nécessaire d’utiliser un mécanisme d’annulation, car certains types de verrous peuvent être utilisés avec await.) Vous pouvez également implémenter cette opération en tant qu’analyseur fourni dans le cadre du runtime.

  • Nous pourrions assouplir la restriction selon laquelle les types de valeur ne peuvent pas être lock.

    • pour le nouveau type de Lock (nécessaire uniquement si la proposition d’API l’a changée de class à struct),
    • pour le modèle général dans lequel tout type peut participer lorsqu’il est implémenté à l’avenir.
  • Nous pourrions autoriser les nouveaux lock dans les méthodes asyncawait n’est pas utilisé à l'intérieur de lock.

    • Actuellement, étant donné que lock est réduit à using avec un ref struct en tant que ressource, cela entraîne une erreur de compilation. La solution de contournement consiste à extraire le lock dans une méthode distincte non async.
    • Au lieu d’utiliser le ref struct Scope, nous pourrions émettre des méthodes Lock.Enter et Lock.Exit dans try/finally. Toutefois, la méthode Exit doit déclencher une exception lorsqu'elle est appelée à partir d'un thread différent de Enter, elle contient donc une recherche de thread qui est évitée lors de l'utilisation de Scope.
    • Il serait préférable de permettre de compiler using sur un ref struct dans les méthodes async s’il n’y a pas de await à l’intérieur du corps using.

Concevoir des réunions

  • LDM 2023-05-01 : décision initiale de soutenir un modèle de lock
  • LDM 2023-10-16 : triage dans l'ensemble de travail pour .NET 9
  • LDM 2023-12-04 : modèle général rejeté, uniquement la casse spéciale du type Lock a été acceptée + ajout d’avertissements d’analyse statique