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.

  • Implementasi publicIAsyncDisposable.DisposeAsync() 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 ke DisposeAsyncCore 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 ke null.
  • Metodenya DisposeAsyncCore() adalah virtual, 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 adalah readonly 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 Utf8JsonWriterdi GitHub.