Implementace metody DisposeAsync
Rozhraní System.IAsyncDisposable bylo zavedeno jako součást C# 8.0. Metodu IAsyncDisposable.DisposeAsync() implementujete, když potřebujete provést vyčištění prostředků, stejně jako při implementaci metody Dispose. Jedním z klíčových rozdílů však je, že tato implementace umožňuje asynchronní operace čištění. Vrátí DisposeAsync() hodnotu ValueTask , která představuje asynchronní operaci odstranění.
Je typické při implementaci IAsyncDisposable rozhraní, které třídy také implementují IDisposable rozhraní. Dobrým vzorem IAsyncDisposable implementace rozhraní je příprava na synchronní nebo asynchronní odstranění, ale není to požadavek. Pokud není možné žádnou synchronní uvolnitelnou třídu, je přijatelné pouze IAsyncDisposable to, že je to možné. Všechny pokyny pro implementaci modelu odstranění platí také pro asynchronní implementaci. Tento článek předpokládá, že už víte, jak implementovat metodu Dispose.
Upozornění
Pokud implementujete IAsyncDisposable rozhraní, ale ne IDisposable rozhraní, může vaše aplikace potenciálně uniknout prostředkům. Pokud třída implementuje IAsyncDisposable, ale ne IDisposable, a příjemce pouze volá Dispose
, vaše implementace by nikdy volání DisposeAsync
. To by vedlo k úniku prostředků.
Tip
Pokud jde o injektáž závislostí, při registraci služeb v rámci IServiceCollectionslužby je životnost služby spravována implicitně vaším jménem. Vyčištění IServiceProvider prostředků a odpovídající IHost orchestrace. Konkrétně implementace IDisposable a IAsyncDisposable jsou řádně uvolněny na konci jejich zadané životnosti.
Další informace naleznete v tématu Injektáž závislostí v .NET.
Prozkoumání DisposeAsync
a DisposeAsyncCore
metody
Rozhraní IAsyncDisposable deklaruje jednu metodu bez parametrů , DisposeAsync(). Každá nezapečetěná třída by měla definovat metodu DisposeAsyncCore()
ValueTask, která také vrací .
Implementace
public
IAsyncDisposable.DisposeAsync() , která nemá žádné parametry.Metoda
protected virtual ValueTask DisposeAsyncCore()
, jejíž podpis je:protected virtual ValueTask DisposeAsyncCore() { }
Metoda DisposeAsync
Metoda public
bez DisposeAsync()
parametrů je volána implicitně v await using
příkazu a jeho účelem je uvolnit nespravované prostředky, provést obecné vyčištění a indikovat, že finalizátor, pokud existuje, nemusí být spuštěn. Uvolnění paměti přidružené ke spravovanému objektu je vždy doménou uvolňování paměti. Z tohoto důvodu má standardní implementaci:
public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore();
// Dispose of unmanaged resources.
Dispose(false);
// Suppress finalization.
GC.SuppressFinalize(this);
}
Poznámka:
Jedním z primárních rozdílů ve vzoru async dispose ve srovnání se vzorem dispose je, že volání metody DisposeAsync() Dispose(bool)
přetížení je dáno false
jako argument. Při implementaci IDisposable.Dispose() metody se však true
předá. To pomáhá zajistit funkční ekvivalenci se vzorem synchronní dispose a dále zajišťuje, že se cesty kódu finalizátoru stále vyvolávají. Jinými slovy, DisposeAsyncCore()
metoda odstraní spravované prostředky asynchronně, takže je nechcete likvidovat synchronně také. Proto místo volání Dispose(false)
Dispose(true)
.
Metoda DisposeAsyncCore
Metoda DisposeAsyncCore()
je určena k provedení asynchronního čištění spravovaných prostředků nebo pro kaskádové volání DisposeAsync()
. Zapouzdřuje běžné asynchronní operace čištění, když podtřída dědí základní třídu, která je implementací IAsyncDisposable. Metoda DisposeAsyncCore()
je virtual
tak, aby odvozené třídy mohly definovat vlastní vyčištění v jejich přepsání.
Tip
Pokud je sealed
implementace IAsyncDisposable , DisposeAsyncCore()
metoda není nutná a asynchronní vyčištění lze provést přímo v IAsyncDisposable.DisposeAsync() metodě.
Implementace vzoru async dispose
Všechny nezapečetěné třídy by měly být považovány za potenciální základní třídu, protože by mohly být zděděné. Pokud implementujete vzor async dispose pro libovolnou potenciální základní třídu, musíte zadat metodu protected virtual ValueTask DisposeAsyncCore()
. Některé z následujících příkladů používají NoopAsyncDisposable
třídu, která je definována takto:
public sealed class NoopAsyncDisposable : IAsyncDisposable
{
ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
}
Tady je příklad implementace vzoru async dispose, který používá typ NoopAsyncDisposable
. Typ implementuje DisposeAsync
vrácením ValueTask.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;
}
}
V předchozím příkladu:
- Je
ExampleAsyncDisposable
nezapečetěná třída, která implementuje IAsyncDisposable rozhraní. - Obsahuje soukromé
IAsyncDisposable
pole,_example
které je inicializováno v konstruktoru. - Metoda
DisposeAsync
deleguje na metoduDisposeAsyncCore
a volání GC.SuppressFinalize , která upozorní systém uvolňování paměti, že finalizátor nemusí běžet. - Obsahuje metodu
DisposeAsyncCore()
, která volá metodu_example.DisposeAsync()
, a nastaví pole nanull
. - Metoda
DisposeAsyncCore()
jevirtual
, která umožňuje podtřídy přepsat ho vlastním chováním.
Zapečetěný alternativní vzor async dispose
Pokud vaše implementovat třídu může být sealed
, můžete implementovat asynchronní dispose vzor přepsáním IAsyncDisposable.DisposeAsync() metody. Následující příklad ukazuje, jak implementovat vzor async dispose pro zapečetěnou třídu:
public sealed class SealedExampleAsyncDisposable : IAsyncDisposable
{
private readonly IAsyncDisposable _example;
public SealedExampleAsyncDisposable() =>
_example = new NoopAsyncDisposable();
public ValueTask DisposeAsync() => _example.DisposeAsync();
}
V předchozím příkladu:
- Je
SealedExampleAsyncDisposable
zapečetěná třída, která implementuje IAsyncDisposable rozhraní. - Pole obsahující
_example
jereadonly
a inicializuje se v konstruktoru. - Metoda
DisposeAsync
volá metodu_example.DisposeAsync()
, implementuje vzor prostřednictvím obsahujícího pole (kaskádové odstranění).
Implementace vzorů dispose i async dispose
Možná budete muset implementovat rozhraní IDisposable i IAsyncDisposable rozhraní, zejména pokud obor třídy obsahuje instance těchto implementací. Tím zajistíte, že můžete správně kaskádně vyčistit volání. Tady je příklad třídy, která implementuje obě rozhraní a ukazuje správné pokyny pro vyčištění.
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;
}
}
IAsyncDisposable.DisposeAsync() Obě implementace IDisposable.Dispose() jsou jednoduchý často používaný kód.
Dispose(bool)
V metodě IDisposable přetížení je instance podmíněně uvolněna, pokud není null
. Instance IAsyncDisposable je přetypována jako IDisposable, a pokud také není null
, je odstraněna také. Obě instance jsou pak přiřazeny .null
S metodou DisposeAsyncCore()
se postupuje stejně jako logický přístup. IAsyncDisposable Pokud instance nenínull
, čeká se DisposeAsync().ConfigureAwait(false)
její volání. IDisposable Pokud je instance také implementací IAsyncDisposable, je také uvolněna asynchronně. Obě instance jsou pak přiřazeny .null
Každá implementace se snaží likvidovat všechny možné uvolnitelné objekty. Tím se zajistí, že se vyčištění správně kaskáduje.
Použití asynchronního jednorázového použití
Chcete-li správně využívat objekt, který implementuje IAsyncDisposable rozhraní, použijete příkaz await a použijete klíčová slova společně. Podívejte se na následující příklad, kde ExampleAsyncDisposable
je třída vytvořena instance a pak zabalena do await using
příkazu.
class ExampleConfigureAwaitProgram
{
static async Task Main()
{
var exampleAsyncDisposable = new ExampleAsyncDisposable();
await using (exampleAsyncDisposable.ConfigureAwait(false))
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
Důležité
ConfigureAwait(IAsyncDisposable, Boolean) Pomocí rozšiřující metody IAsyncDisposable rozhraní můžete nakonfigurovat, jak se pokračování úkolu zařadí do původního kontextu nebo plánovače. Další informace najdete v ConfigureAwait
tématu Nejčastější dotazy ke konfiguraci Await.
V situacích, kdy není použití ConfigureAwait
potřeba, await using
může být příkaz zjednodušen takto:
class ExampleUsingStatementProgram
{
static async Task Main()
{
await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
Kromě toho může být zapsána tak, aby používala implicitní určení rozsahu deklarace using.
class ExampleUsingDeclarationProgram
{
static async Task Main()
{
await using var exampleAsyncDisposable = new ExampleAsyncDisposable();
// Interact with the exampleAsyncDisposable instance.
Console.ReadLine();
}
}
Několik klíčových slov await v jednom řádku
Někdy se await
klíčové slovo může objevit několikrát v jednom řádku. Představte si například následující kód:
await using var transaction = await context.Database.BeginTransactionAsync(token);
V předchozím příkladu:
- Metoda BeginTransactionAsync je očekávána.
- Návratový typ je DbTransaction, který implementuje
IAsyncDisposable
. - Používá se
transaction
asynchronně a také očekává.
Skládané použití
V situacích, kdy vytváříte a používáte více objektů, které implementují IAsyncDisposable, je možné, že příkazy stackingu await using
s ConfigureAwait můžou bránit volání DisposeAsync() v chybných podmínkách. Pokud chcete zajistit, aby DisposeAsync() se vždy volala, měli byste se vyhnout vytváření zásobníků. Následující tři příklady kódu ukazují přijatelné vzory pro použití.
Přijatelný vzor 1
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();
}
}
V předchozím příkladu je každá asynchronní operace čištění explicitně vymezena pod blokem await using
. Vnější obor se řídí tím, jak objOne
nastaví jeho složené závorky, ohraničující objTwo
, jako takový objTwo
je uvolněn jako první, následovaný objOne
. Obě IAsyncDisposable
instance mají DisposeAsync() očekávanou metodu, takže každá instance provádí svou asynchronní operaci čištění. Volání jsou vnořená, ne skládaná.
Přijatelný vzor 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();
}
}
V předchozím příkladu je každá asynchronní operace čištění explicitně vymezena pod blokem await using
. Na konci každého bloku má odpovídající IAsyncDisposable
instance svou DisposeAsync() metodu očekávanou, a proto provádí asynchronní operaci čištění. Volání jsou sekvenční, ne skládaná. V tomto scénáři objOne
se nejprve odstraní a pak objTwo
se vyhodí.
Přijatelný vzor 3
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();
}
}
V předchozím příkladu je každá asynchronní operace čištění implicitně vymezena tělem obsahující metodu. Na konci ohraničujícího bloku IAsyncDisposable
instance provádějí své asynchronní operace čištění. Tento příklad se spouští v obráceném pořadí, ze kterého byly deklarovány, což znamená, že objTwo
je uvolněn před objOne
.
Nepřijatelný vzor
Zvýrazněné řádky v následujícím kódu ukazují, co znamená mít "skládané použití". Pokud je vyvolána výjimka z konstruktoru AnotherAsyncDisposable
, není žádný objekt správně odstraněn. Proměnná objTwo
se nikdy nepřiřazuje, protože konstruktor nebyl úspěšně dokončen. V důsledku toho je konstruktor AnotherAsyncDisposable
zodpovědný za likvidaci všech prostředků přidělených před vyvolání výjimky. Pokud má ExampleAsyncDisposable
typ finalizátor, je způsobilý k dokončení.
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();
}
}
Tip
Vyhněte se tomuto vzoru, protože by to mohlo vést k neočekávanému chování. Pokud použijete některý z přijatelných vzorů, problém nedispozovaných objektů neexistuje. Operace čištění se správně provádějí, když using
nejsou příkazy naskládané.
Viz také
Příklad IDisposable
duální implementace a IAsyncDisposable
podívejte se na Utf8JsonWriter zdrojový kód na GitHubu.