Sdílet prostřednictvím


objekt Lock

Poznámka

Tento článek je specifikace funkce. Specifikace slouží jako návrhový dokument pro funkci. Zahrnuje navrhované změny specifikace spolu s informacemi potřebnými při návrhu a vývoji funkce. Tyto články se publikují, dokud nebudou navrhované změny specifikace finalizovány a začleněny do aktuální specifikace ECMA.

Mezi specifikací funkce a dokončenou implementací může docházet k nějakým nesrovnalostem. Tyto rozdíly jsou zachyceny v příslušných poznámkách ze schůzky návrhu jazyka (LDM) .

Další informace o procesu přijetí specifikací funkcí do jazyka C# najdete v článku o specifikacích .

Problém šampiona: https://github.com/dotnet/csharplang/issues/7104

Shrnutí

Zvláštní případ, jak System.Threading.Lock interaguje s klíčovým slovem lock (voláním jeho metody EnterScope na pozadí). Pokud je to možné, přidejte upozornění statické analýzy, abyste zabránili náhodnému zneužití typu.

Motivace

.NET 9 představuje nový typ System.Threading.Lock jako lepší alternativu k existujícímu uzamčení založenému na monitorování. Přítomnost klíčového slova lock v jazyce C# může vést vývojáře k tomu, aby si mysleli, že ho můžou používat s tímto novým typem. Tím by nedošlo k uzamčení podle sémantiky tohoto typu, ale bylo by s ním zacházeno jako s jakýmkoli jiným objektem a bylo by použito uzamčení na základě monitoru.

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

Podrobný návrh

Sémantika příkazu zámku (§13.13) jsou změněny tak, aby speciálně zpracovaly typ System.Threading.Lock.

Příkaz lock typu lock (x) { ... }

  1. , kde x je výraz typu System.Threading.Lock, je přesně ekvivalentní:
    using (x.EnterScope())
    {
        ...
    }
    
    a System.Threading.Lock musí mít následující tvar:
    namespace System.Threading
    {
        public sealed class Lock
        {
            public Scope EnterScope();
    
            public ref struct Scope
            {
                public void Dispose();
            }
        }
    }
    
  2. kde x je výraz typu reference , je přesně ekvivalentní: [...]

Všimněte si, že tvar nemusí být plně ověřen (např. pokud typ Lock není sealed), ale funkce nemusí fungovat podle očekávání (například při převodu Lock na odvozený typ nebudou vydána žádná upozornění, protože funkce předpokládá neexistenci odvozených typů).

Kromě toho jsou při povyšování typu přidána nová upozornění (System.Threading.Lock):

Implicitní převody odkazů jsou:

  • Z libovolného reference_type do object a dynamic.
    • je hlášeno upozornění, pokud je známo, že reference_type je System.Threading.Lock.
  • Z libovolného class_typeS do libovolného class_typeT, za předpokladu, že S je odvozeno z T.
    • je hlášeno upozornění, když S je známo, že je System.Threading.Lock.
  • Z libovolného class_typeS na libovolný interface_typeT, za předpokladu, že S implementuje T.
    • je hlášeno upozornění, když S je známo, že je System.Threading.Lock.
  • [...]
object l = new System.Threading.Lock(); // warning
lock (l) { } // monitor-based locking is used here

Všimněte si, že k tomuto upozornění dochází i u ekvivalentních explicitních převodů.

Kompilátor se v některých případech vyhne hlášení upozornění v případě, že po převodu na objectnejde instanci uzamknout:

  • pokud je převod implicitní a součástí vyvolání operátoru rovnosti objektu.
var l = new System.Threading.Lock();
if (l != null) // no warning even though `l` is implicitly converted to `object` for `operator!=(object, object)`
    // ...

Pokud chcete utéct z upozornění a vynutit použití uzamčení založeného na monitorování, můžete použít

  • obvyklé prostředky potlačení upozornění (#pragma warning disable),
  • přímo rozhraní Monitor API
  • nepřímé přetypování jako object AsObject<T>(T l) => (object)l;.

Alternativy

  • Podpora obecného vzoru, který mohou používat i jiné typy k interakci s klíčovým slovem lock. Jedná se o budoucí práci, která by mohla být implementována, když se ref structmohou účastnit generických typů. Popsáno v LDM 2023-12-04.

  • Abychom se vyhnuli nejednoznačnosti mezi stávajícím uzamykáním založeným na monitorování a novým Lock (nebo vzorem v budoucnu), mohli bychom:

    • Místo opětovného použití existujícího příkazu lock zavést novou syntaxi.
    • Vyžadovat, aby nové typy zámků byly struct(protože stávající lock nepovolují typy hodnot). Pokud mají struktury opožděné inicializace, může dojít k problémům s výchozími konstruktory a kopírováním.
  • Generátor kódu lze zpevnit proti ukončení vláken (které jsou samy o sobě zastaralé).

  • Můžeme také upozornit, když se Lock předává jako parametr typu, protože uzamčení parametru typu vždy používá uzamčení pomocí monitoru:

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

    To by však mohlo způsobit upozornění při ukládání Locks do seznamu, který je nežádoucí:

    List<Lock> list = new();
    list.Add(new Lock()); // would warn here
    
  • Mohli bychom zahrnout statickou analýzu, abychom zabránili použití System.Threading.Lock v usinga await. Můžeme například vygenerovat chybu nebo upozornění pro kód, jako je using (lockVar.EnterScope()) { await ... }. V současné době to není potřeba, protože Lock.Scope je ref struct, takže kód je přesto neplatný. Pokud bychom ale někdy povolili ref structve async metodách nebo změnili Lock.Scope tak, aby to nebyla ref struct, tato analýza by byla přínosná. (Pro všechny typy zámků, které odpovídají obecnému vzoru, bychom mohli zvážit při implementaci v budoucnu. I když může být nutné mít mechanismus opt-out, protože některé typy zámků mohou být povoleny pro použití s await.) Alternativně by to mohlo být implementováno jako analyzátor dodávaný jako součást běhového prostředí.

  • Mohli bychom zmírnit omezení, že typy hodnot nemohou být lock.

    • pro nový typ Lock (pouze pokud ho návrh rozhraní API změnil z class na struct),
    • pro obecný vzor, kde se jakýkoli typ může zapojit při budoucí implementaci.
  • Nové lock bychom mohli povolit ve async metodách, kdy se v awaitnepoužívá lock .

    • Protože je lock snížen na using s využitím ref struct jako prostředku, dojde k chybě při kompilaci. Alternativním řešením je extrahovat lock do samostatné metody oddělené odasync.
    • Místo použití ref struct Scopebychom mohli v Lock.EnterLock.Exittrygenerovat metody / a finally . Nicméně metoda Exit musí vyvolat výjimku, když je volána z jiného vlákna než Enter, proto obsahuje vyhledávání vláken, kterému se vyhneme při použití Scope.
    • Nejlepší je povolit kompilaci using na ref struct v async metodách, pokud v těle await neexistuje using.

Designérské schůzky

  • LDM 2023-05-01: počáteční rozhodnutí o podpoře modelu lock
  • LDM 2023-10-16: zařazeno do pracovní skupiny pro .NET 9
  • LDM 2023-12-04: odmítl obecný vzor, přijal pouze speciální případ pro typ Lock a přidal varování statické analýzy