Dela via


Implementera en DisposeAsync-metod

Gränssnittet System.IAsyncDisposable introducerades som en del av C# 8.0. Du implementerar IAsyncDisposable.DisposeAsync() metoden när du behöver utföra resursrensning, precis som när du implementerar en Dispose-metod. En av de viktigaste skillnaderna är dock att den här implementeringen möjliggör asynkrona rensningsåtgärder. DisposeAsync() Returnerar en ValueTask som representerar den asynkrona borttagningsåtgärden.

Det är typiskt när du implementerar gränssnittet IAsyncDisposable som klasserna också implementerar IDisposable gränssnittet. Ett bra implementeringsmönster för IAsyncDisposable gränssnittet är att förbereda för synkron eller asynkron borttagning, men det är inte ett krav. Om ingen synkron disponibel av din klass är möjlig är det acceptabelt att bara IAsyncDisposable ha. All vägledning för att implementera mönstret för bortskaffande gäller även för den asynkrona implementeringen. Den här artikeln förutsätter att du redan är bekant med hur du implementerar en Dispose-metod.

Varning

Om du implementerar IAsyncDisposable gränssnittet men inte IDisposable gränssnittet kan din app potentiellt läcka resurser. Om en klass implementerar IAsyncDisposable, men inte IDisposableoch en konsument bara anropar Dispose, skulle implementeringen aldrig anropa DisposeAsync. Detta skulle resultera i en resursläcka.

Dricks

När det gäller beroendeinmatning hanteras tjänstlivslängden implicit för din räkning när du registrerar tjänster i en IServiceCollection. Och IServiceProvider motsvarande orkestrera IHost resursrensning. Mer specifikt tas implementeringarna av IDisposable och IAsyncDisposable bort i slutet av den angivna livslängden.

Mer information finns i Beroendeinmatning i .NET.

Utforska DisposeAsync och DisposeAsyncCore metoder

Gränssnittet IAsyncDisposable deklarerar en enda parameterlös metod, DisposeAsync(). Alla icke-sealerade klasser bör definiera en DisposeAsyncCore() metod som också returnerar en ValueTask.

  • En public IAsyncDisposable.DisposeAsync() implementering som inte har några parametrar.

  • En protected virtual ValueTask DisposeAsyncCore() metod vars signatur är:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

Metoden DisposeAsync

Den public parameterlösa DisposeAsync() metoden anropas implicit i en await using -instruktion, och dess syfte är att frigöra ohanterade resurser, utföra allmän rensning och att ange att finalizern, om den finns, inte behöver köras. Att frigöra minnet som är associerat med ett hanterat objekt är alltid domänen för skräpinsamlaren. På grund av detta har den en standardimplementering:

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

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

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

Kommentar

En primär skillnad i mönstret för async-bortskaffande jämfört med mönstret för bortskaffande är att anropet från DisposeAsync() till Dispose(bool) överlagringsmetoden anges false som ett argument. Vid implementering av IDisposable.Dispose() metoden skickas dock true i stället. Detta bidrar till att säkerställa funktionell likvärdighet med det synkrona mönstret för bortskaffande och säkerställer ytterligare att finalizer-kodsökvägarna fortfarande anropas. Med andra ord DisposeAsyncCore() kommer metoden att ta bort hanterade resurser asynkront, så du vill inte ta bort dem synkront också. Anropa därför Dispose(false) i stället Dispose(true)för .

Metoden DisposeAsyncCore

Metoden DisposeAsyncCore() är avsedd att utföra asynkron rensning av hanterade resurser eller för sammanhängande anrop till DisposeAsync(). Den kapslar in de vanliga asynkrona rensningsåtgärderna när en underklass ärver en basklass som är en implementering av IAsyncDisposable. Metoden DisposeAsyncCore() är virtual så att härledda klasser kan definiera anpassad rensning i sina åsidosättningar.

Dricks

Om en implementering av IAsyncDisposable är sealedDisposeAsyncCore() behövs inte metoden och den asynkrona rensningen IAsyncDisposable.DisposeAsync() kan utföras direkt i metoden.

Implementera mönstret för asynkron autentisering

Alla icke-ealerade klasser bör betraktas som en potentiell basklass eftersom de kan ärvas. Om du implementerar mönstret async dispose för en potentiell basklass måste du ange protected virtual ValueTask DisposeAsyncCore() metoden. Några av följande exempel använder en NoopAsyncDisposable klass som definieras på följande sätt:

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

Här är ett exempel på en implementering av mönstret async dispose som använder NoopAsyncDisposable typen . Typen implementerar DisposeAsync genom att ValueTask.CompletedTaskreturnera .

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;
    }
}

I exemplet ovan händer följande:

  • ExampleAsyncDisposable är en icke-öppen klass som implementerar IAsyncDisposable gränssnittet.
  • Den innehåller ett privat IAsyncDisposable fält, _example, som initieras i konstruktorn.
  • Metoden DisposeAsync delegerar till DisposeAsyncCore metoden och anropar GC.SuppressFinalize för att meddela skräpinsamlaren att finalizern inte behöver köras.
  • Den innehåller en DisposeAsyncCore() metod som anropar _example.DisposeAsync() metoden och anger fältet till null.
  • Metoden DisposeAsyncCore() är virtual, som gör att underklasser kan åsidosätta den med anpassat beteende.

Förseglat alternativt asynkront bortskaffningsmönster

Om implementeringsklassen kan vara sealedkan du implementera mönstret async dispose genom att IAsyncDisposable.DisposeAsync() åsidosätta metoden. I följande exempel visas hur du implementerar mönstret async dispose för en förseglad klass:

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

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

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

I exemplet ovan händer följande:

  • SealedExampleAsyncDisposable är en förseglad klass som implementerar IAsyncDisposable gränssnittet.
  • Det innehållande _example fältet är readonly och initieras i konstruktorn.
  • Metoden DisposeAsync anropar _example.DisposeAsync() metoden och implementerar mönstret via det innehållande fältet (sammanhängande bortskaffande).

Implementera både bortskaffnings- och asynkrona bortskaffningsmönster

Du kan behöva implementera både gränssnitten IDisposable och IAsyncDisposable , särskilt när ditt klassomfång innehåller instanser av dessa implementeringar. Detta säkerställer att du kan rensa anrop på rätt sätt. Här är en exempelklass som implementerar båda gränssnitten och visar rätt vägledning för rensning.

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;
    }
}

Implementeringarna IDisposable.Dispose() och IAsyncDisposable.DisposeAsync() är båda enkla exempelkod.

Dispose(bool) I överlagringsmetoden tas instansen IDisposable villkorligt bort om den inte nullär . Instansen är gjuten IAsyncDisposable som IDisposable, och om den inte nullheller är , tas den också bort. Båda instanserna tilldelas sedan till null.

DisposeAsyncCore() Med metoden följs samma logiska metod. Om instansen IAsyncDisposable inte nullär väntar dess anrop till DisposeAsync().ConfigureAwait(false) . Om instansen IDisposable också är en implementering av IAsyncDisposabletas den även bort asynkront. Båda instanserna tilldelas sedan till null.

Varje implementering strävar efter att ta bort alla möjliga engångsobjekt. Detta säkerställer att rensningen är korrekt överlappande.

Använda asynkron disponibel

Om du vill använda ett objekt som implementerar IAsyncDisposable gränssnittet korrekt använder du inväntningen och använder nyckelord tillsammans. Tänk på följande exempel, där ExampleAsyncDisposable klassen instansieras och sedan omsluts i en await using instruktion.

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

        Console.ReadLine();
    }
}

Viktigt!

ConfigureAwait(IAsyncDisposable, Boolean) Använd tilläggsmetoden för IAsyncDisposable gränssnittet för att konfigurera hur fortsättningen av uppgiften ska ordnas enligt den ursprungliga kontexten eller schemaläggaren. Mer information om ConfigureAwaitfinns i ConfigureAwait FAQ (Vanliga frågor och svar om ConfigureAwait).

I situationer där användningen av ConfigureAwait inte behövs kan -instruktionen await using förenklas på följande sätt:

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

        Console.ReadLine();
    }
}

Dessutom kan det skrivas för att använda den implicita omfånget för en användningsdeklaration.

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

        // Interact with the exampleAsyncDisposable instance.

        Console.ReadLine();
    }
}

Flera väntande nyckelord på en enda rad

Ibland kan nyckelordet await visas flera gånger inom en enda rad. Tänk till exempel på följande kod:

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

I exemplet ovan händer följande:

Staplade användningar

I situationer där du skapar och använder flera objekt som implementerar IAsyncDisposableär det möjligt att stackningsinstruktioner await using med ConfigureAwait kan förhindra anrop till DisposeAsync() under felaktiga förhållanden. För att säkerställa att det DisposeAsync() alltid anropas bör du undvika stapling. Följande tre kodexempel visar godkända mönster att använda i stället.

Acceptabelt mönster ett


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();
    }
}

I föregående exempel begränsas varje asynkron rensningsåtgärd uttryckligen under await using blocket. Det yttre omfånget följer hur objOne dess klammerparenteser, som omsluter objTwo, objTwo tas bort först, följt av objOne. Båda IAsyncDisposable instanserna väntar på sin DisposeAsync() metod, så varje instans utför sin asynkrona rensningsåtgärd. Anropen är kapslade, inte staplade.

Acceptabelt mönster två

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();
    }
}

I föregående exempel begränsas varje asynkron rensningsåtgärd uttryckligen under await using blocket. I slutet av varje block väntar motsvarande IAsyncDisposable instans på sin DisposeAsync() metod och utför därmed sin asynkrona rensningsåtgärd. Anropen är sekventiella, inte staplade. I det här scenariot objOne tas bort först och tas sedan objTwo bort.

Acceptabelt mönster tre

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();
    }
}

I föregående exempel begränsas varje asynkron rensningsåtgärd implicit med den innehållande metodtexten. I slutet av omslutningsblocket IAsyncDisposable utför instanserna sina asynkrona rensningsåtgärder. Det här exemplet körs i omvänd ordning från vilken de deklarerades, vilket innebär att tas objTwo bort före objOne.

Oacceptabelt mönster

De markerade raderna i följande kod visar vad det innebär att ha "staplade användningar". Om ett undantag utlöses från konstruktorn tas inget av objekten AnotherAsyncDisposable bort korrekt. Variabeln objTwo tilldelas aldrig eftersom konstruktorn inte slutfördes. Därför ansvarar konstruktorn för för AnotherAsyncDisposable att ta bort alla resurser som allokerats innan det utlöser ett undantag. Om typen ExampleAsyncDisposable har en finalizer är den berättigad till slutförande.

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();
    }
}

Dricks

Undvik det här mönstret eftersom det kan leda till oväntat beteende. Om du använder något av de acceptabla mönstren är problemet med icke-lagrade objekt obefintlig. Rensningsåtgärderna utförs korrekt när using instruktioner inte staplas.

Se även

Ett exempel på dubbel implementering av IDisposable och finns i källkoden Utf8JsonWriter på GitHubIAsyncDisposable.