實作 DisposeAsync 方法

介面 System.IAsyncDisposable 是 C# 8.0 的一部分引進。 IAsyncDisposable.DisposeAsync()當您需要執行資源清除時實作 方法,就像實作 Dispose 方法一樣。 不過,其中一個主要差異在於,此實作允許非同步清除作業。 會 DisposeAsync() 傳回 , ValueTask 表示非同步處置作業。

實作 IAsyncDisposable 類別也會實作介面時,通常會是 IDisposable 一般情況。 介面的良好實作 IAsyncDisposable 模式是準備進行同步或非同步處置,不過這不是必要條件。 如果無法同步處置您的類別,則只能 IAsyncDisposable 接受擁有 。 實作處置模式的所有指導方針也適用于非同步實作。 本文假設您已經熟悉如何 實作 Dispose 方法

警告

如果您實作 IAsyncDisposable 介面,但不是 IDisposable 介面,您的應用程式可能會流失資源。 如果類別實作 ,但不是 IDisposable ,而且取用 IAsyncDisposable 者只會呼叫 Dispose ,則您的實作永遠不會呼叫 DisposeAsync 。 這會導致資源流失。

提示

關於相依性插入,在 中 IServiceCollection 註冊服務時,會代表您隱含管理 服務存留期IServiceProvider和對應的 IHost 協調資源清除。 具體而言,和 IAsyncDisposableIDisposable 實作會在指定的存留期結束時正確處置。

如需詳細資訊,請參閱 .NET 中的相依性插入

探索 DisposeAsyncDisposeAsyncCore 方法

介面 IAsyncDisposable 會宣告單一無參數方法 DisposeAsync() 。 任何非密封類別都應該有另 DisposeAsyncCore() 一個也會傳 ValueTask 回 的方法。

  • publicIAsyncDisposable.DisposeAsync() 沒有參數的實作。

  • protected virtual ValueTask DisposeAsyncCore() 章為的方法:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

DisposeAsync 方法

publicDisposeAsync() 參數方法會在 語句中 await using 隱含呼叫,其用途是釋放 Unmanaged 資源、執行一般清除,以及指出如果有完成項,則不需要執行。 釋放與 Managed 物件相關聯的記憶體一律是 垃圾收集行程的網域。 因此,它擁有標準實作:

public async ValueTask DisposeAsync()
{
    // Perform async cleanup.
    await DisposeAsyncCore();

    // Dispose of unmanaged resources.
    Dispose(false);

    // Suppress finalization.
    GC.SuppressFinalize(this);
}

注意

與處置模式相比,非同步處置模式的主要差異之一,就是從 DisposeAsync()Dispose(bool) 載方法呼叫會指定 false 為引數。 不過,實作 IDisposable.Dispose() 方法時會 true 改為傳遞。 這有助於確保與同步處置模式的功能相等,並進一步確保仍會叫用完成項程式碼路徑。 換句話說, DisposeAsyncCore() 方法會以非同步方式處置 Managed 資源,因此您也不想以同步方式處置它們。 因此,呼叫 Dispose(false) 而不是 Dispose(true)

DisposeAsyncCore 方法

方法 DisposeAsyncCore() 旨在執行 Managed 資源的非同步清除,或對 的串聯呼叫 DisposeAsync() 。 當子類別繼承實作 的 IAsyncDisposable 基類時,它會封裝常見的非同步清除作業。 方法 DisposeAsyncCore()virtual 可讓衍生類別在其覆寫中定義額外的清除。

提示

如果 的實作 IAsyncDisposablesealedDisposeAsyncCore() 則不需要 方法,而且可以在 方法中 IAsyncDisposable.DisposeAsync() 直接執行非同步清除。

實作非同步處置模式

所有非密封類別都應該視為潛在的基類,因為它們可能會繼承。 如果您針對任何可能的基類實作非同步處置模式,則必須提供 protected virtual ValueTask DisposeAsyncCore() 方法。 下列部分範例使用 NoopAsyncDisposable 定義如下的類別:

public sealed class NoopAsyncDisposable : IAsyncDisposable
{
    ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
}

以下是非同步處置模式的範例實作,其使用藉由傳回 ValueTask.CompletedTask 實作 的 DisposeAsync 自訂 NoopAsyncDisposable 型別。

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;
    }
}

在上述範例中:

  • ExampleAsyncDisposable是實作 介面的非密封類別 IAsyncDisposable
  • 它包含建 IAsyncDisposable 構函式中初始化的私人欄位 _example
  • 方法 DisposeAsync 會委派給 DisposeAsyncCore 方法,並呼叫 GC.SuppressFinalize 以通知垃圾收集行程,完成項不需要執行。
  • 它包含呼叫 DisposeAsyncCore() 方法的方法 _example.DisposeAsync() ,並將 欄位設定為 null
  • 方法 DisposeAsyncCore()virtual ,並在 類別中 ExampleAsyncDisposable 覆寫。

密封的替代非同步處置模式

如果您的實作類別可以是 sealed ,您可以覆 IAsyncDisposable.DisposeAsync() 寫 方法來實作非同步處置模式。 下列範例示範如何實作密封類別的非同步處置模式:

public sealed class SealedExampleAsyncDisposable : IAsyncDisposable
{
    private readonly IAsyncDisposable _example;

    public SealedExampleAsyncDisposable() =>
        _example = new NoopAsyncDisposable();

    public ValueTask DisposeAsync() => _example.DisposeAsync();
}

在上述範例中:

  • SealedExampleAsyncDisposable是實作 介面的 IAsyncDisposable 密封類別。
  • 包含 _example 的欄位為 readonly ,並在建構函式中初始化。
  • 方法會 DisposeAsync_example.DisposeAsync() 呼叫 方法,透過包含的欄位實作模式, (級聯處置) 。

實作 dispose 和 async dispose 模式

您可能需要同時 IDisposable 實作 和 IAsyncDisposable 介面,特別是當您的類別範圍包含這些實作的實例時。 這麼做可確保您可以適當地串聯清除呼叫。 以下是實作兩個介面的範例類別,並示範清理的適當指引。

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();
            (_asyncDisposableResource as IDisposable)?.Dispose();
            _disposableResource = null;
            _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;
    }
}

IDisposable.Dispose()IAsyncDisposable.DisposeAsync() 實作都是簡單的重複使用程式碼。

在多載方法中 Dispose(bool)IDisposable ,如果 實例不是 null ,則會有條件地處置 。 實例 IAsyncDisposable 會轉換成 IDisposable ,如果它也是 null ,也會處置它。 然後,這兩個實例都會指派給 null

DisposeAsyncCore()使用 方法時,會遵循相同的邏輯方法。 IAsyncDisposable如果 實例不是 null ,則會等候其對 DisposeAsync().ConfigureAwait(false) 的呼叫。 IDisposable如果 實例也是 的實作 IAsyncDisposable ,它也會以非同步方式處置。 然後,這兩個實例都會指派給 null

使用非同步可處置

若要正確地取用實作 IAsyncDisposable 介面的物件,您可以使用 await並使用關鍵字 。 請考慮下列範例,其中 類別 ExampleAsyncDisposable 會具現化,然後包裝在 語句中 await using

class ExampleConfigureAwaitProgram
{
    static async Task Main()
    {
        var exampleAsyncDisposable = new ExampleAsyncDisposable();
        await using (exampleAsyncDisposable.ConfigureAwait(false))
        {
            // Interact with the exampleAsyncDisposable instance.
        }

        Console.ReadLine();
    }
}

重要

ConfigureAwait(IAsyncDisposable, Boolean)使用 介面的 IAsyncDisposable 擴充方法,設定如何在其原始內容或排程器上封送處理工作接續。 如需 的詳細資訊 ConfigureAwait ,請參閱 ConfigureAwait 常見問題

對於不需要 使用 ConfigureAwait 的情況, await using 可以簡化 語句,如下所示:

class ExampleUsingStatementProgram
{
    static async Task Main()
    {
        await using (var exampleAsyncDisposable = new ExampleAsyncDisposable())
        {
            // Interact with the exampleAsyncDisposable instance.
        }

        Console.ReadLine();
    }
}

此外,也可以寫入以使用 using 宣告的隱含範圍。

class ExampleUsingDeclarationProgram
{
    static async Task Main()
    {
        await using var exampleAsyncDisposable = new ExampleAsyncDisposable();

        // Interact with the exampleAsyncDisposable instance.

        Console.ReadLine();
    }
}

單行中的多個 await 關鍵字

await有時候關鍵字可能會在單行內出現多次。 例如,請考慮下列程式碼:

await using var transaction = await context.Database.BeginTransactionAsync(token);

在上述範例中:

堆疊 using

在建立和使用實作 的多個 物件 IAsyncDisposable 的情況下,使用 堆疊 await using 語句 ConfigureAwait 可能會防止在異常狀況中呼叫 DisposeAsync() 。 若要確保 DisposeAsync() 一律呼叫 ,您應該避免堆疊。 下列三個程式碼範例顯示改用的可接受的模式。

可接受的模式 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();
    }
}

在上述範例中,每個非同步清除作業都會明確限定在 await using 區塊底下。 外部範圍的定義方式是 objOne 設定其大括弧、括住 objTwo ,例如 objTwo 先處置,後面接著 objOne 。 這兩 IAsyncDisposable 個實例都有等候的方法 DisposeAsync() ,因此每個實例都會執行其非同步清除作業。 呼叫是巢狀的,不會堆疊。

可接受的模式 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();
    }
}

在上述範例中,每個非同步清除作業都會明確限定在 await using 區塊底下。 在每個區塊結束時,對應的 IAsyncDisposable 實例會等候其 DisposeAsync() 方法,因此執行其非同步清除作業。 呼叫是循序的,不會堆疊。 在此案例 objOne 中,會先處置,然後 objTwo 處置。

可接受的模式三

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();
    }
}

在上述範例中,每個非同步清除作業都會隱含地以包含的方法主體來設定範圍。 在封入區塊結束時, IAsyncDisposable 實例會執行其非同步清除作業。 這會以宣告它們的反向循序執行,這表示 objTwo 在 之前 objOne 處置。

無法接受的模式

下列程式碼中反白顯示的行會顯示「堆疊使用」的意義。 如果從 AnotherAsyncDisposable 建構函式擲回例外狀況,則不會正確處置兩個物件。 objTwo變數永遠不會指派,因為建構函式未順利完成。 因此,的 AnotherAsyncDisposable 建構函式會負責處置在擲回例外狀況之前配置的任何資源。 ExampleAsyncDisposable如果類型具有完成項,則其符合最終處理資格。

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();
    }
}

提示

避免此模式,因為它可能會導致非預期的行為。 如果您使用其中一個可接受的模式,則未散發的物件問題不存在。 當語句未堆疊時 using ,會正確執行清除作業。

另請參閱

如需 和 IAsyncDisposable 的雙重實作 IDisposable 範例,請參閱 Utf8JsonWriterGitHub 上的原始程式碼。