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 sealed
DisposeAsyncCore()
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 tillDisposeAsyncCore
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 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 sealed
kan 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 ärreadonly
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 null
heller ä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 ConfigureAwait
finns 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
. transaction
Anvä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
.