Terapkan metode DisposeAsync
Antarmuka System.IAsyncDisposable diperkenalkan sebagai bagian dari C# 8.0. Anda menerapkan metode IAsyncDisposable.DisposeAsync() ketika Anda perlu melakukan pembersihan sumber daya, seperti yang Anda lakukan saat menerapkan metode Dispose. Namun, salah satu perbedaan utamanya adalah implementasi ini memungkinkan operasi pembersihan asinkron. mengembalikan DisposeAsync() ValueTask yang mewakili operasi pembuangan asinkron.
Ini khas saat mengimplementasikan IAsyncDisposable antarmuka yang juga mengimplementasikan IDisposable antarmuka. Pola implementasi antarmuka yang IAsyncDisposable baik adalah disiapkan untuk pembuangan sinkron atau asinkron, namun, itu bukan persyaratan. Jika kelas Anda tidak dapat digunakan secara sinkron, hanya IAsyncDisposable dapat diterima. Semua panduan untuk menerapkan pola pembuangan juga berlaku untuk implementasi asinkron. Artikel ini mengasumsikan bahwa Anda sudah terbiasa dengan cara menerapkan metode Dispose.
Perhatian
Jika Anda menerapkan IAsyncDisposable antarmuka tetapi bukan IDisposable antarmuka, aplikasi Anda berpotensi membocorkan sumber daya. Jika kelas menerapkan IAsyncDisposable, tetapi tidak IDisposable, dan konsumen hanya memanggil Dispose
, implementasi Anda tidak akan pernah memanggil DisposeAsync
. Ini akan mengakibatkan kebocoran sumber daya.
Tip
Berkenaan dengan injeksi dependensi, saat mendaftarkan layanan di IServiceCollection, masa hidup layanan dikelola secara implisit atas nama Anda. IServiceProvider dan IHost yang sesuai mengatur pembersihan sumber daya. Secara khusus, implementasi IDisposable dan IAsyncDisposable dibuang dengan benar pada akhir masa pakai yang ditentukan.
Untuk informasi selengkapnya, lihat Injeksi dependensi.
Jelajahi DisposeAsync
dan DisposeAsyncCore
metode
Antarmuka IAsyncDisposable mendeklarasikan metode tanpa parameter tunggal, DisposeAsync(). Setiap kelas nonseal harus menentukan DisposeAsyncCore()
metode yang juga mengembalikan ValueTask.
public
IAsyncDisposable.DisposeAsync() Implementasi yang tidak memiliki parameter.Metode
protected virtual ValueTask DisposeAsyncCore()
yang tanda tangannya adalah:protected virtual ValueTask DisposeAsyncCore() { }
Metode DisposeAsync
Metode public
tanpa parameter DisposeAsync()
disebut secara implisit dalam sebuah pernyataan await using
, dan tujuannya adalah untuk membebaskan sumber daya yang tidak dikelola, melakukan pembersihan umum, dan untuk menunjukkan bahwa finalizer, jika ada, tidak perlu dijalankan. Membebaskan memori sebenarnya yang terkait dengan objek terkelola selalu menjadi domain pengumpul sampah. Karena itu, ia memiliki implementasi standar:
public async ValueTask DisposeAsync()
{
// Perform async cleanup.
await DisposeAsyncCore();
// Dispose of unmanaged resources.
Dispose(false);
// Suppress finalization.
GC.SuppressFinalize(this);
}
Catatan
Salah satu perbedaan utama dalam pola pembuangan asinkron dibandingkan dengan pola pembuangan, adalah bahwa panggilan dari DisposeAsync() ke metode kelebihan beban Dispose(bool)
diberikan false
sebagai argumen. Namun, saat menerapkan metode IDisposable.Dispose() ini, true
diteruskan sebagai gantinya. Ini membantu memastikan kesetaraan fungsional dengan pola pembuangan sinkron, dan selanjutnya memastikan bahwa jalur kode finalizer masih dipanggil. Dengan kata lain, metode DisposeAsyncCore()
akan membuang sumber daya terkelola secara asinkron, sehingga Anda tidak ingin membuangnya secara sinkron juga. Oleh karena itu, panggil Dispose(false)
alih-alih Dispose(true)
.
Metode DisposeAsyncCore
Metode DisposeAsyncCore()
dimaksudkan untuk melakukan pembersihan asinkron sumber daya terkelola atau untuk panggilan bertingkat ke DisposeAsync()
. Ini merangkum operasi pembersihan asinkron umum ketika subkelas mewarisi kelas dasar yang merupakan implementasi dari IAsyncDisposable. Metode DisposeAsyncCore()
ini virtual
agar kelas turunan dapat menentukan pembersihan kustom dalam penimpaannya.
Tip
Jika implementasi IAsyncDisposable adalah sealed
, metode DisposeAsyncCore()
tidak diperlukan, dan pembersihan asinkron dapat dilakukan langsung dalam metode IAsyncDisposable.DisposeAsync().
Menerapkan pola pembuangan asinkron
Semua kelas nonseal harus dianggap sebagai kelas dasar potensial, karena dapat diwariskan. Jika Anda menerapkan pola pembuangan asinkron untuk setiap kelas dasar potensial, Anda harus menyediakan metode protected virtual ValueTask DisposeAsyncCore()
. Beberapa contoh berikut menggunakan NoopAsyncDisposable
kelas yang didefinisikan sebagai berikut:
public sealed class NoopAsyncDisposable : IAsyncDisposable
{
ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
}
Berikut adalah contoh implementasi pola pembuangan asinkron yang menggunakan jenis .NoopAsyncDisposable
Jenis mengimplementasikan DisposeAsync
dengan mengembalikan 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;
}
}
Dalam contoh sebelumnya:
ExampleAsyncDisposable
adalah kelas nonsealed yang mengimplementasikan IAsyncDisposable antarmuka.- Ini berisi bidang privat
IAsyncDisposable
,_example
, yang diinisialisasi dalam konstruktor. - Metode ini
DisposeAsync
mendelegasikan keDisposeAsyncCore
metode dan panggilan GC.SuppressFinalize untuk memberi tahu pengumpul sampah bahwa finalizer tidak perlu dijalankan. - Ini berisi
DisposeAsyncCore()
metode yang memanggil_example.DisposeAsync()
metode , dan mengatur bidang kenull
. - Metodenya
DisposeAsyncCore()
adalahvirtual
, yang memungkinkan subkelas untuk mengambil alihnya dengan perilaku kustom.
Pola pembuangan asinkron alternatif yang disegel
Jika kelas penerapan Anda bisa sealed
, Anda dapat menerapkan pola pembuangan asinkron dengan mengesampingkan IAsyncDisposable.DisposeAsync() metode . Contoh berikut menunjukkan cara menerapkan pola pembuangan asinkron untuk kelas yang disegel:
public sealed class SealedExampleAsyncDisposable : IAsyncDisposable
{
private readonly IAsyncDisposable _example;
public SealedExampleAsyncDisposable() =>
_example = new NoopAsyncDisposable();
public ValueTask DisposeAsync() => _example.DisposeAsync();
}
Dalam contoh sebelumnya:
SealedExampleAsyncDisposable
adalah kelas tertutup yang mengimplementasikan IAsyncDisposable antarmuka.- Bidang yang berisi
_example
adalahreadonly
dan diinisialisasi dalam konstruktor. - Metode ini
DisposeAsync
memanggil_example.DisposeAsync()
metode , menerapkan pola melalui bidang yang berisi (pembuangan berkakade).
Menerapkan pola buang dan async dispose
Anda mungkin perlu mengimplementasikan kedua antarmuka IDisposable dan IAsyncDisposable, terutama ketika cakupan kelas Anda berisi instans implementasi ini. Melakukan hal itu memastikan bahwa Anda dapat mengatur panggilan pembersihan dengan benar. Berikut adalah kelas contoh yang mengimplementasikan antarmuka dan menunjukkan panduan yang tepat untuk pembersihan.
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;
}
}
Implementasi IDisposable.Dispose() dan IAsyncDisposable.DisposeAsync() keduanya adalah kode boilerplate sederhana.
Dispose(bool)
Dalam metode kelebihan beban, IDisposable instans dibuang secara kondisional jika bukan null
. Instans IAsyncDisposable dilemparkan sebagai IDisposable, dan jika juga tidak null
, instans juga dibuang. Kedua instans kemudian ditetapkan ke null
.
Dengan metode DisposeAsyncCore()
, pendekatan logis yang sama diikuti. IAsyncDisposable Jika instans tidak null
, panggilannya ke DisposeAsync().ConfigureAwait(false)
ditunggu. Jika instans IDisposable juga merupakan implementasi dari IAsyncDisposable, instans juga dibuang secara asinkron. Kedua instans kemudian ditetapkan ke null
.
Setiap implementasi berusaha untuk membuang semua kemungkinan objek sekali pakai. Ini memastikan bahwa pembersihan berskala dengan benar.
Menggunakan async sekali pakai
Untuk mengonsumsi objek IAsyncDisposable yang mengimplementasikan antarmuka dengan benar, Anda menggunakan kata kunci await dan using bersamaan. Pertimbangkan contoh berikut, di mana kelas ExampleAsyncDisposable
dibuat dan kemudian dibungkus dalam pernyataan await using
.
class ExampleConfigureAwaitProgram
{
static async Task Main()
{
var exampleAsyncDisposable = new ExampleAsyncDisposable();
await using (exampleAsyncDisposable.ConfigureAwait(false))
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
Penting
Gunakan metode ekstensi ConfigureAwait(IAsyncDisposable, Boolean) dari antarmuka IAsyncDisposable untuk mengonfigurasi bagaimana kelanjutan tugas di marshalled pada konteks atau penjadwal aslinya. Untuk informasi lebih lanjut ConfigureAwait
, lihat Tanya Jawab Umum ConfigureAwait.
Untuk situasi di mana penggunaan ConfigureAwait
tidak diperlukan, await using
pernyataan dapat disederhanakan sebagai berikut:
class ExampleUsingStatementProgram
{
static async Task Main()
{
await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
{
// Interact with the exampleAsyncDisposable instance.
}
Console.ReadLine();
}
}
Selain itu, dapat ditulis untuk menggunakan cakupan implisit dari penggunaan declaration.
class ExampleUsingDeclarationProgram
{
static async Task Main()
{
await using var exampleAsyncDisposable = new ExampleAsyncDisposable();
// Interact with the exampleAsyncDisposable instance.
Console.ReadLine();
}
}
Beberapa kata kunci menunggu dalam satu baris
Terkadang kata kunci await
mungkin muncul beberapa kali dalam satu baris. Sebagai contoh, perhatikan kode berikut:
await using var transaction = await context.Database.BeginTransactionAsync(token);
Dalam contoh sebelumnya:
- Mmetode BeginTransactionAsync ditunggu.
- Jenis pengembalian adalah DbTransaction, yang mengimplementasikan
IAsyncDisposable
. transaction
digunakan secara asinkron, dan juga ditunggu.
Penggunaan ditumpuk
Dalam situasi di mana Anda membuat dan menggunakan beberapa objek yang mengimplementasikan IAsyncDisposable, ada kemungkinan bahwa pernyataan tumpukan await using
dengan ConfigureAwait dapat mencegah panggilan ke DisposeAsync() dalam kondisi yang salah. Untuk memastikan bahwa DisposeAsync() selalu dipanggil, Anda harus menghindari penumpukan. Tiga contoh kode berikut menunjukkan pola yang dapat diterima untuk digunakan sebagai gantinya.
Pola yang dapat diterima satu
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();
}
}
Dalam contoh sebelumnya, setiap operasi pembersihan asinkron secara eksplisit dicakup di bawah await using
blok. Cakupan luar mengikuti bagaimana objOne
mengatur kurung kurawalnya, yang mengapit objTwo
, seperti objTwo
itu dibuang terlebih dahulu, diikuti oleh objOne
. Kedua IAsyncDisposable
instans memiliki metode yang DisposeAsync() ditunggu, sehingga setiap instans melakukan operasi pembersihan asinkronnya. Panggilan bersarang, tidak ditumpuk.
Pola yang dapat diterima dua
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();
}
}
Dalam contoh sebelumnya, setiap operasi pembersihan asinkron secara eksplisit dicakup di bawah await using
blok. Di akhir setiap blok, instans yang IAsyncDisposable
sesuai memiliki metode yang DisposeAsync() ditunggu, sehingga melakukan operasi pembersihan asinkronnya. Panggilan berurutan, tidak ditumpuk. Dalam skenario objOne
dibuang terlebih dahulu, kemudian objTwo
dibuang.
Pola yang dapat diterima tiga
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();
}
}
Dalam contoh sebelumnya, setiap operasi pembersihan asinkron secara implisit dicakup dengan isi metode yang berisi. Di akhir blok penutup, IAsyncDisposable
instans melakukan operasi pembersihan asinkron mereka. Contoh ini berjalan dalam urutan terbalik dari mana mereka dideklarasikan, yang berarti yang objTwo
dibuang sebelum objOne
.
Pola yang tidak dapat diterima
Baris yang disorot dalam kode berikut menunjukkan apa artinya "penggunaan ditumpuk". Jika pengecualian dilemparkan dari konstruktor AnotherAsyncDisposable
, tidak ada objek yang dibuang dengan benar. Variabel objTwo
tidak pernah ditetapkan karena konstruktor tidak berhasil diselesaikan. Akibatnya, konstruktor AnotherAsyncDisposable
yang bertanggung jawab untuk membuang sumber daya apa pun yang dialokasikan sebelum melemparkan pengecualian. ExampleAsyncDisposable
Jika jenis memiliki finalizer, jenis tersebut memenuhi syarat untuk finalisasi.
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
Hindari pola ini karena dapat menyebabkan perilaku yang tidak terduga. Jika Anda menggunakan salah satu pola yang dapat diterima, masalah objek yang tidak diposisikan tidak ada. Operasi pembersihan dilakukan dengan benar ketika using
pernyataan tidak ditumpuk.
Lihat juga
Untuk contoh implementasi ganda dari IDisposable
dan IAsyncDisposable
, lihat kode sumber Utf8JsonWriter di GitHub.