Delen via


Lock-object

Notitie

Dit artikel is een functiespecificatie. De specificatie fungeert als het ontwerpdocument voor de functie. Het bevat voorgestelde specificatiewijzigingen, samen met informatie die nodig is tijdens het ontwerp en de ontwikkeling van de functie. Deze artikelen worden gepubliceerd totdat de voorgestelde specificaties zijn voltooid en opgenomen in de huidige ECMA-specificatie.

Er kunnen enkele verschillen zijn tussen de functiespecificatie en de voltooide implementatie. De verschillen zijn vastgelegd in de relevante verslagen (Language Design Meeting).

Meer informatie over het proces voor het aannemen van functiespeclets in de C#-taalstandaard vindt u in het artikel over de specificaties.

Probleem met Champion: https://github.com/dotnet/csharplang/issues/7104

Samenvatting

Speciaal geval voor hoe System.Threading.Lock samenwerkt met het lock trefwoord (waarbij de EnterScope methode achter de schermen wordt aangeroepen). Voeg waar mogelijk statische analysewaarschuwingen toe om onbedoeld misbruik van het type te voorkomen.

Motivatie

.NET 9 introduceert een nieuw System.Threading.Lock type als een beter alternatief voor bestaande monitor-gebaseerde vergrendeling. De aanwezigheid van het lock trefwoord in C# kan ertoe leiden dat ontwikkelaars denken dat ze het kunnen gebruiken met dit nieuwe type. Dit zou niet worden vergrendeld volgens de semantiek van dit type, maar zou het als een gewoon object behandelen en gebruikmaken van monitor-gebaseerde vergrendeling.

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

Gedetailleerd ontwerp

Semantiek van de vergrendelingsinstructie (§13.13) wordt gewijzigd om een speciaal geval te maken voor het System.Threading.Lock type.

Een lock verklaring van de vorm lock (x) { ... }

  1. waarbij x een expressie van het type System.Threading.Lockis, precies gelijk is aan:
    using (x.EnterScope())
    {
        ...
    }
    
    en System.Threading.Lock moeten de volgende vorm hebben:
    namespace System.Threading
    {
        public sealed class Lock
        {
            public Scope EnterScope();
    
            public ref struct Scope
            {
                public void Dispose();
            }
        }
    }
    
  2. wanneer x een expressie is van een reference_type, precies gelijk is aan: [...]

Houd er rekening mee dat de shape mogelijk niet volledig is ingeschakeld (er zijn bijvoorbeeld geen fouten of waarschuwingen als het Lock type niet is sealed), maar de functie werkt mogelijk niet zoals verwacht (er zijn bijvoorbeeld geen waarschuwingen bij het converteren van Lock naar een afgeleid type, omdat de functie ervan uitgaat dat er geen afgeleide typen zijn).

Daarnaast worden er nieuwe waarschuwingen toegevoegd aan impliciete verwijzingsconversies (§10.2.8) bij het upcasten van het System.Threading.Lock type:

De impliciete verwijzingsconversies zijn:

  • Van elke reference_type tot object en dynamic.
    • een waarschuwing wordt gemeld wanneer de reference_type bekend is System.Threading.Lock.
  • Van elke class_typeS tot een class_typeT, mits S is afgeleid van T.
    • Er wordt een waarschuwing gerapporteerd wanneer S bekend is als System.Threading.Lock.
  • Van elke class_typeS naar een interface_typeT, mits STimplementeert.
    • Er wordt een waarschuwing gerapporteerd wanneer het bekend is dat SSystem.Threading.Lockis.
  • [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here

Houd er rekening mee dat deze waarschuwing optreedt, zelfs voor gelijkwaardige expliciete conversies.

De compiler vermijdt het rapporteren van de waarschuwing in sommige gevallen wanneer het exemplaar niet kan worden vergrendeld nadat het is geconverteerd naar object:

  • wanneer de conversie impliciet is en deel uitmaakt van een aanroep van een object gelijkheidsoperator.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
    // ...

Als u de waarschuwing wilt negeren en toch vergrendeling op basis van monitor wilt gebruiken, kunt u

  • de gebruikelijke middelen voor het onderdrukken van waarschuwingen (#pragma warning disable),
  • api's rechtstreeks Monitor,
  • indirect gieten zoals object AsObject<T>(T l) => (object)l;.

Alternatieven

  • Ondersteuning voor een algemeen patroon dat andere typen ook kunnen gebruiken om te communiceren met het lock trefwoord. Dit is een toekomstig werk dat kan worden geïmplementeerd wanneer ref structs kunnen deelnemen aan generics. Besproken in LDM 2023-12-04.

  • Om dubbelzinnigheid tussen de bestaande monitorvergrendeling en de nieuwe Lock (of patroon in de toekomst) te voorkomen, kunnen we het volgende doen:

    • Introduceer een nieuwe syntaxis in plaats van de bestaande lock-instructie opnieuw te gebruiken.
    • Vereisen dat de nieuwe vergrendelingstypen structzijn (omdat de bestaande lock waardetypen niet toestaan). Er kunnen problemen zijn met standaardconstructors en kopiëren als de structs luie initialisatie hebben.
  • Het gegenereerde code kan worden versterkt om bestand te zijn tegen het afbreken van threads (die zelf al verouderd zijn).

  • We kunnen ook waarschuwen wanneer Lock wordt doorgegeven als een typeparameter, omdat het vergrendelen van een typeparameter altijd gebruikmaakt van op monitor gebaseerde vergrendeling:

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

    Dit zou echter waarschuwingen veroorzaken bij het opslaan van Locks in een lijst die ongewenst is:

    List<Lock> list = new();
    list.Add(new Lock()); // would warn here
    
  • We kunnen statische analyses opnemen om het gebruik van System.Threading.Lock in usingmet awaits te voorkomen. Dat wil zeggen, we kunnen een fout of een waarschuwing geven voor code zoals using (lockVar.EnterScope()) { await ... }. Dit is momenteel niet nodig omdat Lock.Scope een ref structis, zodat code toch illegaal is. Als we echter ooit ref structin async methoden hebben toegestaan of Lock.Scope hebben gewijzigd om geen ref structte zijn, zou deze analyse nuttig worden. (We moeten waarschijnlijk ook rekening houden met deze vergrendelingstypen die overeenkomen met het algemene patroon als deze in de toekomst worden geïmplementeerd. Hoewel er mogelijk een opt-outmechanisme moet zijn, omdat sommige vergrendelingstypen mogelijk kunnen worden gebruikt met await.) U kunt dit ook implementeren als een analyse die wordt verzonden als onderdeel van de runtime.

  • We zouden de beperking dat waardetypen niet kunnen worden locked kunnen versoepelen.

    • voor het nieuwe Lock type (alleen nodig als het API-voorstel dit heeft gewijzigd van class in struct),
    • voor het algemene patroon waarbij elk type kan deelnemen wanneer dit in de toekomst wordt geïmplementeerd.
  • We kunnen de nieuwe lock in async methoden toestaan waarbij await niet in de lockwordt gebruikt.

    • Omdat lock momenteel wordt verlaagd tot using met een ref struct als de resource, resulteert dit in een compilatietijdfout. De tijdelijke oplossing is om de lock te extraheren in een aparte methode die geenasync is.
    • In plaats van de ref struct Scopete gebruiken, kunnen we Lock.Enter en Lock.Exit methoden in try/finallyverzenden. De methode Exit moet echter worden gegooid wanneer deze wordt aangeroepen vanuit een andere thread dan Enter, waardoor deze een threadzoekactie bevat die wordt vermeden bij het gebruik van de Scope.
    • Het beste zou zijn om compilatie van using op een ref struct in deze async-methoden toe te staan, op voorwaarde dat er geen await in de using-hoofdtekst aanwezig is.

Ontwerpvergaderingen

  • LDM 2023-05-01: eerste beslissing om een lock patroon te ondersteunen
  • LDM 2023-10-16: ingedeeld in de werkset voor .NET 9
  • LDM 2023-12-04: het algemene patroon afgewezen, alleen het type Lock met speciale behuizing geaccepteerd en waarschuwingen voor statische analyse toevoegen