Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
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 - publicIAsyncDisposable.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 IAsyncDisposablefält,_example, som initieras i konstruktorn.
- Metoden DisposeAsyncdelegerar tillDisposeAsyncCoremetoden 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 tillnull.
- Metoden DisposeAsyncCore()ärvirtual, 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 _examplefältet ärreadonlyoch initieras i konstruktorn.
- Metoden DisposeAsyncanropar_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:
- Metoden BeginTransactionAsync väntar.
- Returtypen är DbTransaction, som implementerar IAsyncDisposable.
- transactionAnvänds asynkront och väntar också.
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.