DisposeAsync metódus implementálása

A System.IAsyncDisposable felület a C# 8.0 részeként lett bevezetve. A metódust akkor IAsyncDisposable.DisposeAsync() implementálhatja, amikor erőforrás-tisztítást kell végrehajtania, ugyanúgy, mint az Elidegenítési metódus implementálásakor. Az egyik legfontosabb különbség azonban az, hogy ez az implementáció lehetővé teszi az aszinkron törlési műveleteket. Az DisposeAsync() aszinkron ártalmatlanítási műveletet képviselő aszinkron értéket ad vissza ValueTask .

A felület implementálásakor jellemző, hogy az IAsyncDisposable osztályok a felületet is implementálják IDisposable . A felület jó implementációs mintáját IAsyncDisposable szinkron vagy aszinkron ártalmatlanításra kell előkészíteni, azonban ez nem követelmény. Ha az osztály szinkron eldobása nem lehetséges, csak IAsyncDisposable elfogadható. Az ártalmatlanítási minta megvalósítására vonatkozó összes útmutató az aszinkron megvalósításra is vonatkozik. Ez a cikk feltételezi, hogy már ismeri az Dispose metódus implementálásának módját.

Figyelemfelhívás

Ha az interfészt IAsyncDisposable implementálja, de nem az IDisposable interfészt, az alkalmazás esetleg kiszivárogtathatja az erőforrásokat. Ha egy osztály implementálja IAsyncDisposable, de nem IDisposable, és egy fogyasztó csak hív Dispose, akkor az implementáció soha nem hívna meg DisposeAsync. Ez erőforrás-szivárgást eredményezne.

Tipp.

A függőséginjektálás tekintetében a szolgáltatások IServiceCollectionregisztrálásakor a szolgáltatás élettartama implicit módon történik az Ön nevében. Az IServiceProvider és a hozzá tartozó IHost vezénylési erőforrás-törlés. Pontosabban az implementációk IDisposableIAsyncDisposable és azok megfelelően vannak megsemmisítve a megadott élettartamuk végén.

További információ: Függőséginjektálás a .NET-ben.

Felfedezés DisposeAsync és DisposeAsyncCore módszerek

Az IAsyncDisposable interfész egyetlen paraméter nélküli metódust deklarál. DisposeAsync() Minden nem összevont osztálynak meg kell határoznia egy metódust DisposeAsyncCore() , amely szintén visszaad egy ValueTask.

  • Olyan publicIAsyncDisposable.DisposeAsync() implementáció, amely nem rendelkezik paraméterekkel.

  • Olyan protected virtual ValueTask DisposeAsyncCore() módszer, amelynek aláírása:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

A DisposeAsync metódus

A public paraméter nélküli DisposeAsync() metódust implicit módon hívjuk meg egy await using utasításban, és a célja a nem felügyelt erőforrások felszabadítása, az általános törlés végrehajtása, valamint annak jelzése, hogy a véglegesítőnek, ha van ilyen, nem kell futtatnia. A felügyelt objektumhoz társított memória felszabadítása mindig a szemétgyűjtő tartománya. Emiatt szabványos implementációval rendelkezik:

public async ValueTask DisposeAsync()
{
    // Perform async cleanup.
    await DisposeAsyncCore();

    // Dispose of unmanaged resources.
    Dispose(false);

    // Suppress finalization.
    GC.SuppressFinalize(this);
}

Feljegyzés

Az aszinkron elidegenítési minta egyik elsődleges különbsége az elidegenítési mintához képest, hogy a túlterhelési metódusra DisposeAsync()Dispose(bool) irányuló hívás argumentumként van megadva false . A metódus megvalósításakor IDisposable.Dispose() azonban true a rendszer át lesz adva. Ez segít biztosítani a szinkron megsemmisítési mintával való funkcionális egyenértékűséget, és biztosítja, hogy a véglegesítő kód elérési útjai továbbra is meghívásra kerülnek. Más szóval a metódus aszinkron módon fogja megsemmisíteni a DisposeAsyncCore() felügyelt erőforrásokat, így nem szeretné szinkron módon megsemmisíteni őket. Ezért a hívás Dispose(false) ahelyett, hogy Dispose(true).

A DisposeAsyncCore metódus

A DisposeAsyncCore() metódus a felügyelt erőforrások aszinkron törlésének vagy a felé irányuló kaszkádolt hívásoknak a végrehajtására DisposeAsync()szolgál. Beágyazza a gyakori aszinkron tisztítási műveleteket, amikor egy alosztály örököl egy alaposztályt, amely a implementációja IAsyncDisposable. A DisposeAsyncCore() módszer célja, hogy a virtual származtatott osztályok egyéni törlést definiálhassanak a felülbírálásokban.

Tipp.

Ha implementációja IAsyncDisposable van sealed, akkor nincs szükség a DisposeAsyncCore() metódusra, és az aszinkron törlés közvetlenül a metódusban IAsyncDisposable.DisposeAsync() is elvégezhető.

Az aszinkron ártalmatlanítási minta implementálása

Minden nem összevont osztályt potenciális alaposztálynak kell tekinteni, mert örökölhetőek lehetnek. Ha implementálja az aszinkron ártalmatlanítási mintát bármely lehetséges alaposztályhoz, meg kell adnia a metódust protected virtual ValueTask DisposeAsyncCore() . Az alábbi példák közül néhány a következőképpen definiált NoopAsyncDisposable osztályt használ:

public sealed class NoopAsyncDisposable : IAsyncDisposable
{
    ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
}

Íme egy példa a típust használó aszinkron ártalmatlanítási minta implementálására NoopAsyncDisposable . A típus a visszatéréssel implementál DisposeAsyncValueTask.CompletedTask.

public class ExampleAsyncDisposable : IAsyncDisposable
{
    private IAsyncDisposable? _example;

    public ExampleAsyncDisposable() =>
        _example = new NoopAsyncDisposable();

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        GC.SuppressFinalize(this);
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_example is not null)
        {
            await _example.DisposeAsync().ConfigureAwait(false);
        }

        _example = null;
    }
}

Az előző példában:

  • Ez ExampleAsyncDisposable egy nem összevont osztály, amely megvalósítja az interfészt IAsyncDisposable .
  • Tartalmaz egy privát IAsyncDisposable mezőt, _exampleamely inicializálva van a konstruktorban.
  • A DisposeAsync metódus delegálja a DisposeAsyncCore metódust, és felhívja GC.SuppressFinalize , hogy értesítse a szemétgyűjtőt, hogy a véglegesítőnek nem kell futnia.
  • Ez egy metódust DisposeAsyncCore() tartalmaz, amely meghívja a metódust _example.DisposeAsync() , és beállítja a mezőt a következőre null: .
  • Ez DisposeAsyncCore() a metódus lehetővé teszi az virtualalosztályok számára, hogy egyéni viselkedéssel felülbírálják azt.

Lezárt alternatív aszinkron megsemmisítési minta

Ha a implementálási osztály lehet sealed, a metódus felülírásával implementálhatja az aszinkron rendezési IAsyncDisposable.DisposeAsync() mintát. Az alábbi példa bemutatja, hogyan implementálható az aszinkron ártalmatlanítási minta egy lezárt osztályhoz:

public sealed class SealedExampleAsyncDisposable : IAsyncDisposable
{
    private readonly IAsyncDisposable _example;

    public SealedExampleAsyncDisposable() =>
        _example = new NoopAsyncDisposable();

    public ValueTask DisposeAsync() => _example.DisposeAsync();
}

Az előző példában:

  • Ez SealedExampleAsyncDisposable egy lezárt osztály, amely megvalósítja az interfészt IAsyncDisposable .
  • A tartalmazó _example mező inicializálva van readonly a konstruktorban.
  • A DisposeAsync metódus meghívja a metódust _example.DisposeAsync() , és implementálja a mintát a tárolómezőn keresztül (kaszkádolt ártalmatlanítás).

Az elidegenítési és az aszinkron megsemmisítési minták implementálása

Előfordulhat, hogy mind az interfészeket, mind a IDisposableIAsyncDisposable felületeket implementálnia kell, különösen akkor, ha az osztály hatóköre tartalmazza az implementációk példányait. Ezzel biztosíthatja, hogy megfelelően kaszkádoltan törölje a hívásokat. Íme egy példaosztály, amely mindkét felületet implementálja, és bemutatja a megfelelő útmutatást a törléshez.

class ExampleConjunctiveDisposableusing : IDisposable, IAsyncDisposable
{
    IDisposable? _disposableResource = new MemoryStream();
    IAsyncDisposable? _asyncDisposableResource = new MemoryStream();

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore().ConfigureAwait(false);

        Dispose(disposing: false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _disposableResource?.Dispose();
            _disposableResource = null;

            if (_asyncDisposableResource is IDisposable disposable)
            {
                disposable.Dispose();
                _asyncDisposableResource = null;
            }
        }
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_asyncDisposableResource is not null)
        {
            await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
        }

        if (_disposableResource is IAsyncDisposable disposable)
        {
            await disposable.DisposeAsync().ConfigureAwait(false);
        }
        else
        {
            _disposableResource?.Dispose();
        }

        _asyncDisposableResource = null;
        _disposableResource = null;
    }
}

A IDisposable.Dispose() megvalósítások és IAsyncDisposable.DisposeAsync() az implementációk egyaránt egyszerű sablonkódok.

A túlterhelési Dispose(bool) metódusban a IDisposable példány feltételesen el lesz osztva, ha nem null. A IAsyncDisposable példányt úgy öntötték el, mint IDisposable, és ha nem nullis, akkor az is el lesz vetve. Ezután mindkét példányhoz hozzá lesz rendelve null.

A metódussal DisposeAsyncCore() ugyanazt a logikai megközelítést követi. Ha a IAsyncDisposable példány nem null, a hívása DisposeAsync().ConfigureAwait(false) vár. Ha a IDisposable példány szintén implementációja IAsyncDisposable, akkor az aszinkron módon is el lesz állítva. Ezután mindkét példányhoz hozzá lesz rendelve null.

Minden megvalósítás arra törekszik, hogy minden lehetséges eldobható objektumot megsemmisítsen. Ez biztosítja, hogy a tisztítás megfelelően kaszkádolt legyen.

Aszinkron eldobható

Az interfészt megvalósító IAsyncDisposable objektumok megfelelő felhasználásához használja együtt a várt és a kulcsszavakat. Vegye figyelembe az alábbi példát, amelyben az ExampleAsyncDisposable osztály példányosítva lesz, majd egy await using utasításba burkolva.

class ExampleConfigureAwaitProgram
{
    static async Task Main()
    {
        var exampleAsyncDisposable = new ExampleAsyncDisposable();
        await using (exampleAsyncDisposable.ConfigureAwait(false))
        {
            // Interact with the exampleAsyncDisposable instance.
        }

        Console.ReadLine();
    }
}

Fontos

ConfigureAwait(IAsyncDisposable, Boolean) A felület bővítménymetódusával IAsyncDisposable konfigurálhatja, hogy a tevékenység folytatása hogyan legyen rendezve az eredeti környezetében vagy ütemezőjén. További információ: ConfigureAwaitConfigureAwait FAQ.

Olyan helyzetekben, amikor nincs szükség a használatra ConfigureAwait , az utasítás az await using alábbiak szerint egyszerűsíthető:

class ExampleUsingStatementProgram
{
    static async Task Main()
    {
        await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
        {
            // Interact with the exampleAsyncDisposable instance.
        }

        Console.ReadLine();
    }
}

Ezenkívül megírható egy használati deklaráció implicit hatókörének használatára is.

class ExampleUsingDeclarationProgram
{
    static async Task Main()
    {
        await using var exampleAsyncDisposable = new ExampleAsyncDisposable();

        // Interact with the exampleAsyncDisposable instance.

        Console.ReadLine();
    }
}

Több várakozó kulcsszavak egyetlen sorban

Előfordulhat, hogy a await kulcsszó többször is megjelenik egy sorban. Vegyük például a következő kódot:

await using var transaction = await context.Database.BeginTransactionAsync(token);

Az előző példában:

  • A BeginTransactionAsync metódust várjuk.
  • A visszatérési típus az DbTransaction, amely megvalósítja IAsyncDisposable.
  • A transaction rendszer aszinkron módon használja, és várja is.

Halmozott használat

Olyan helyzetekben, amikor több objektumot hoz létre és használ, amelyek implementálhatókIAsyncDisposable, lehetséges, hogy az utasítások ConfigureAwait halmozása await using megakadályozhatja a hibás körülmények között érkező DisposeAsync() hívásokat. Annak érdekében, hogy ezt DisposeAsync() mindig meghívja, kerülnie kell a halmozást. Az alábbi három példakód elfogadható mintákat mutat be.

Elfogadható minta


class ExampleOneProgram
{
    static async Task Main()
    {
        var objOne = new ExampleAsyncDisposable();
        await using (objOne.ConfigureAwait(false))
        {
            // Interact with the objOne instance.

            var objTwo = new ExampleAsyncDisposable();
            await using (objTwo.ConfigureAwait(false))
            {
                // Interact with the objOne and/or objTwo instance(s).
            }
        }

        Console.ReadLine();
    }
}

Az előző példában minden aszinkron törlési művelet explicit hatókörrel rendelkezik a await using blokk alatt. A külső hatókör azt követi, hogyan objOne állítja be a kapcsos zárójeleket, és hogyan helyezi el elsőként a kapcsos zárójeleket objTwo, majd objTwo a következőt objOne: . Mindkét IAsyncDisposable példányra vár a DisposeAsync() metódus, ezért minden példány végrehajtja az aszinkron tisztítási műveletet. A hívások beágyazottak, nem halmozottak.

Elfogadható minta 2

class ExampleTwoProgram
{
    static async Task Main()
    {
        var objOne = new ExampleAsyncDisposable();
        await using (objOne.ConfigureAwait(false))
        {
            // Interact with the objOne instance.
        }

        var objTwo = new ExampleAsyncDisposable();
        await using (objTwo.ConfigureAwait(false))
        {
            // Interact with the objTwo instance.
        }

        Console.ReadLine();
    }
}

Az előző példában minden aszinkron törlési művelet explicit hatókörrel rendelkezik a await using blokk alatt. Az egyes blokkok végén a megfelelő IAsyncDisposable példány metódusa DisposeAsync() vár, így végrehajtja az aszinkron tisztítási műveletet. A hívások szekvenciálisak, nem halmozottak. Ebben a forgatókönyvben objOne először a megsemmisítés, majd objTwo a megsemmisítés történik.

Három elfogadható minta

class ExampleThreeProgram
{
    static async Task Main()
    {
        var objOne = new ExampleAsyncDisposable();
        await using var ignored1 = objOne.ConfigureAwait(false);

        var objTwo = new ExampleAsyncDisposable();
        await using var ignored2 = objTwo.ConfigureAwait(false);

        // Interact with objOne and/or objTwo instance(s).

        Console.ReadLine();
    }
}

Az előző példában minden aszinkron tisztítási művelet implicit módon hatókörbe van helyezve a tartalmazó metódus törzsével. A beágyazási blokk végén a példányok elvégzik az IAsyncDisposable aszinkron tisztítási műveleteket. Ez a példa fordított sorrendben fut, amelyből deklarálták őket, ami azt jelenti, hogy objTwo a program korábban objOneelveti őket.

Elfogadhatatlan minta

A következő kód kiemelt sorai azt mutatják, hogy mit jelent a "halmozott használat". Ha a konstruktor kivételt AnotherAsyncDisposable jelez, egyik objektum sem lesz megfelelően megsemmisítve. A változó objTwo soha nem lesz hozzárendelve, mert a konstruktor nem fejeződött be sikeresen. Ennek eredményeképpen a konstruktor AnotherAsyncDisposable felelős a kivétel kiosztása előtt lefoglalt erőforrások eltávolításáért. Ha a ExampleAsyncDisposable típus rendelkezik véglegesítővel, jogosult a véglegesítésre.

class DoNotDoThisProgram
{
    static async Task Main()
    {
        var objOne = new ExampleAsyncDisposable();
        // Exception thrown on .ctor
        var objTwo = new AnotherAsyncDisposable();

        await using (objOne.ConfigureAwait(false))
        await using (objTwo.ConfigureAwait(false))
        {
            // Neither object has its DisposeAsync called.
        }

        Console.ReadLine();
    }
}

Tipp.

Kerülje ezt a mintát, mert az váratlan viselkedéshez vezethet. Ha az elfogadható minták egyikét használja, a nem feltárt objektumok problémája nem létezik. A tisztítási műveletek helyesen lesznek végrehajtva, ha using az utasítások nincsenek halmozva.

Lásd még

A gitHubon található forráskódban kettős megvalósítási IDisposableIAsyncDisposablepéldát találhatUtf8JsonWriter.