实现 DisposeAsync 方法Implement a DisposeAsync method

已将 System.IAsyncDisposable 接口作为 C# 8.0 的一部分引入。The System.IAsyncDisposable interface was introduced as part of C# 8.0. 需要执行资源清理时,可以实现 IAsyncDisposable.DisposeAsync() 方法,就像实现 Dispose 方法一样。You implement the IAsyncDisposable.DisposeAsync() method when you need to perform resource cleanup, just as you would when implementing a Dispose method. 但是,其中一个主要区别是,此实现允许异步清理操作。One of the key differences however, is that this implementation allows for asynchronous cleanup operations. DisposeAsync() 返回表示异步释放操作的 ValueTaskThe DisposeAsync() returns a ValueTask that represents the asynchronous dispose operation.

通常,当实现 IAsyncDisposable 接口时,类还将实现 IDisposable 接口。It is typical when implementing the IAsyncDisposable interface that classes will also implement the IDisposable interface. IAsyncDisposable 接口的一种良好实现模式是为同步或异步释放做好准备。A good implementation pattern of the IAsyncDisposable interface is to be prepared for either synchronous or asynchronous dispose. 用于实现释放模式的所有指南也适用于异步实现。All of the guidance for implementing the dispose pattern also applies to the asynchronous implementation. 本文假设你已熟悉如何实现 Dispose 方法This article assumes that you're already familiar with how to implement a Dispose method.

DisposeAsync() 和 DisposeAsyncCore()DisposeAsync() and DisposeAsyncCore()

IAsyncDisposable 接口声明单个无参数方法 DisposeAsync()The IAsyncDisposable interface declares a single parameterless method, DisposeAsync(). 任何非密封类都应具有另外一个也返回 ValueTaskDisposeAsyncCore() 方法。Any non-sealed class should have an additional DisposeAsyncCore() method that also returns a ValueTask.

  • 没有参数的 public IAsyncDisposable.DisposeAsync() 实现。A public IAsyncDisposable.DisposeAsync() implementation that has no parameters.

  • 一个 protected virtual ValueTask DisposeAsyncCore() 方法,其签名为:A protected virtual ValueTask DisposeAsyncCore() method whose signature is:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

DisposeAsync() 方法The DisposeAsync() method

public 无参数的 DisposeAsync() 方法在 await using 语句中隐式调用,其用途是释放非托管资源,执行常规清理,以及指示终结器(如果存在)不必运行。The public parameterless DisposeAsync() method is called implicitly in an await using statement, and its purpose is to free unmanaged resources, perform general cleanup, and to indicate that the finalizer, if one is present, need not run. 释放与托管对象关联的内存始终是垃圾回收器的域。Freeing the memory associated with a managed object is always the domain of the garbage collector. 因此,它具有标准实现:Because of this, it has a standard implementation:

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

    // Dispose of unmanaged resources.
    Dispose(false);
    // Suppress finalization.
    GC.SuppressFinalize(this);
}

备注

与释放模式相比,异步释放模式的主要差异在于,从 DisposeAsync()Dispose(bool) 重载方法的调用被赋予 false 作为参数。One primary difference in the async dispose pattern compared to the dispose pattern, is that the call from DisposeAsync() to the Dispose(bool) overload method is given false as an argument. 但实现 IDisposable.Dispose() 方法时,改为传递 trueWhen implementing the IDisposable.Dispose() method, however, true is passed instead. 这有助于确保与同步释放模式的功能等效性,并进一步确保仍调用终结器代码路径。This helps ensure functional equivalence with the synchronous dispose pattern, and further ensures that finalizer code paths still get invoked. 换句话说,DisposeAsyncCore() 方法将异步释放托管资源,因此不希望也同步释放这些资源。In other words, the DisposeAsyncCore() method will dispose of managed resources asynchronously, so you don't want to dispose of them synchronously as well. 因此,调用 Dispose(false) 而非 Dispose(true)Therefore, call Dispose(false) instead of Dispose(true).

DisposeAsyncCore() 方法The DisposeAsyncCore() method

DisposeAsyncCore() 方法旨在执行受管理资源的异步清理,或对 DisposeAsync() 执行级联调用。The DisposeAsyncCore() method is intended to perform the asynchronous cleanup of managed resources or for cascading calls to DisposeAsync(). 当子类继承作为 IAsyncDisposable 的实现的基类时,它会封装常见的异步清理操作。It encapsulates the common asynchronous cleanup operations when a subclass inherits a base class that is an implementation of IAsyncDisposable. DisposeAsyncCore() 方法是 virtual,以便派生类可以在其重写中定义其他清理。The DisposeAsyncCore() method is virtual so that derived classes can define additional cleanup in their overrides.

提示

如果 IAsyncDisposable 的实现是 sealed,则不需要 DisposeAsyncCore() 方法,异步清理可直接在 IAsyncDisposable.DisposeAsync() 方法中执行。If an implementation of IAsyncDisposable is sealed, the DisposeAsyncCore() method is not needed, and the asynchronous cleanup can be performed directly in the IAsyncDisposable.DisposeAsync() method.

实现异步释放模式Implement the async dispose pattern

所有非密封类都应被视为潜在的基类,因为它们可以被继承。All non-sealed classes should be considered a potential base class, because they could be inherited. 如果为任何潜在基类实现异步释放模式,则必须提供 protected virtual ValueTask DisposeAsyncCore() 方法。If you implement the async dispose pattern for any potential base class, you must provide the protected virtual ValueTask DisposeAsyncCore() method. 下面是使用 System.Text.Json.Utf8JsonWriter 的异步释放模式的实现示例。Here is an example implementation of the async dispose pattern that uses a System.Text.Json.Utf8JsonWriter.

using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

public class ExampleAsyncDisposable : IAsyncDisposable, IDisposable
{
    private Utf8JsonWriter _jsonWriter = new Utf8JsonWriter(new MemoryStream());

    public void Dispose()
    {
        Dispose(disposing: true);
        GC.SuppressFinalize(this);
    }

    public async ValueTask DisposeAsync()
    {
        await DisposeAsyncCore();

        Dispose(disposing: false);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposing)
        {
            _jsonWriter?.Dispose();
        }

        _jsonWriter = null;
    }

    protected virtual async ValueTask DisposeAsyncCore()
    {
        if (_jsonWriter is not null)
        {
            await _jsonWriter.DisposeAsync();
        }

        _jsonWriter = null;
    }
}

前面的示例使用 Utf8JsonWriterThe preceding example uses the Utf8JsonWriter. 有关 System.Text.Json 的详细信息,请参阅如何从 Newtonsoft.Json 迁移到 System.Text.JsonFor more information about System.Text.Json, see How to migrate from Newtonsoft.Json to System.Text.Json.

同时实现释放模式和异步释放模式Implement both dispose and async dispose patterns

可能需要同时实现 IDisposableIAsyncDisposable 接口,尤其是当类范围包含这些实现的实例时。You may need to implement both the IDisposable and IAsyncDisposable interfaces, especially when your class scope contains instances of these implementations. 这样做可确保你可以正确地级联清理调用。Doing so ensures that you can properly cascade clean up calls. 下面是一个示例类,它实现两个接口并演示清理的正确指导。Here is an example class that implements both interfaces and demonstrates the proper guidance for cleanup.

using System;
using System.IO;
using System.Threading.Tasks;

namespace Samples
{
    public class CustomDisposable : 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();

            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() 实现都是简单的样板代码。The IDisposable.Dispose() and IAsyncDisposable.DisposeAsync() implementations are both simple boilerplate code.

Dispose(bool) 重载方法中,如果 IDisposable 实例不为 null,则有条件地将其释放。In the Dispose(bool) overload method, the IDisposable instance is conditionally disposed of if it is not null. IAsyncDisposable 实例被强制转换为 IDisposable,如果该实例也不为 null,也将被释放。The IAsyncDisposable instance is cast as IDisposable, and if it is also not null, it is disposed of as well. 然后,将这两个实例都分配给 nullBoth instances are then assigned to null.

使用 DisposeAsyncCore() 方法时,遵循相同的逻辑方法。With the DisposeAsyncCore() method, the same logical approach is followed. 如果 IAsyncDisposable 实例不为 null,则等待其对 DisposeAsync().ConfigureAwait(false) 的调用。If the IAsyncDisposable instance is not null, its call to DisposeAsync().ConfigureAwait(false) is awaited. 如果 IDisposable 实例也是 IAsyncDisposable 的实现,也将其异步释放。If the IDisposable instance is also an implementation of IAsyncDisposable, it's also disposed of asynchronously. 然后,将这两个实例都分配给 nullBoth instances are then assigned to null.

使用异步释放Using async disposable

要正确使用实现 IAsyncDisposable 接口的对象,请将 awaitusing 关键字结合使用。To properly consume an object that implements the IAsyncDisposable interface, you use the await and using keywords together. 请考虑以下示例,其中 ExampleAsyncDisposable 类进行了实例化,然后包装在 await using 语句中。Consider the following example, where the ExampleAsyncDisposable class is instantiated and then wrapped in an await using statement.

using System;
using System.Threading.Tasks;

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

        Console.ReadLine();
    }
}

重要

使用 IAsyncDisposable 接口的 ConfigureAwait(IAsyncDisposable, Boolean) 扩展方法配置延续任务在其原始上下文或计划程序上的封送方式。Use the ConfigureAwait(IAsyncDisposable, Boolean) extension method of the IAsyncDisposable interface to configure how the continuation of the task is marshalled on its original context or scheduler. 有关 ConfigureAwait 的详细信息,请参阅 ConfigureAwait FAQFor more information on ConfigureAwait, see ConfigureAwait FAQ.

对于不需要使用 ConfigureAwait 的情况,可以按如下所示简化 await using 语句:For situations where the usage of ConfigureAwait is not needed, the await using statement could be simplified as follows:

using System;
using System.Threading.Tasks;

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

        Console.ReadLine();
    }
}

此外,它还可以编写为使用 using 声明的隐式范围。Furthermore, it could be written to use the implicit scoping of a using declaration.

using System;
using System.Threading.Tasks;

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

        // Interact with the exampleAsyncDisposable instance.

        Console.ReadLine();
    }
}

堆叠的 usingStacked usings

在创建和使用实现 IAsyncDisposable 的多个对象的情况下,残存错误条件中具有 ConfigureAwait 的堆叠 await using 语句可能会阻止调用 DisposeAsync()In situations where you create and use multiple objects that implement IAsyncDisposable, it's possible that stacking await using statements with ConfigureAwait could prevent calls to DisposeAsync() in errant conditions. 若要确保始终调用 DisposeAsync(),应避免堆叠。To ensure that DisposeAsync() is always called, you should avoid stacking. 下面的三个代码示例显示要改用的可接受模式。The following three code examples show acceptable patterns to use instead.

可接受的模式一Acceptable pattern one

using System;
using System.Threading.Tasks;

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 块下。In the preceding example, each asynchronous clean up operation is explicitly scoped under the await using block. 外部范围由 objOne 设置其大括号的方法来定义;若将 objTwo 括起来,这样就会先处理 objTwo,然后处理 objOneThe outer scope is defined by how objOne sets its braces, enclosing objTwo, as such objTwo is disposed first, followed by objOne. 这两个 IAsyncDisposable 实例都使三种 DisposeAsync() 方法等待,从而执行其异步清理操作。Both IAsyncDisposable instances have there DisposeAsync() methods awaited, thus performing its asynchronous clean up operation. 嵌套调用,而不是堆叠调用。The calls are nested, not stacked.

可接受的模式二Acceptable pattern two

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 块下。In the preceding example, each asynchronous clean up operation is explicitly scoped under the await using block. 在每个块的末尾,相应的 IAsyncDisposable 实例使其 DisposeAsync() 方法等待,从而执行其异步清理操作。At the end of each block, the corresponding IAsyncDisposable instance has its DisposeAsync() method awaited, thus performing its asynchronous clean up operation. 按顺序排列调用,而不是堆叠调用。The calls are sequential, not stacked. 在此场景中,首先处理 objOne,然后处理 objTwoIn this scenario objOne is disposed first, then objTwo is disposed.

可接受的模式三Acceptable pattern three

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

在前面的示例中,每个异步清理操作都通过包含的方法主体隐式限定了范围。In the preceding example, each asynchronous clean up operation is implicitly scoped with the containing method body. 在封闭块的末尾,IAsyncDisposable 实例执行其异步清理操作。At the end of the enclosing block, the IAsyncDisposable instances perform their asynchronous clean up operations. 此运行顺序与它们声明的顺序相反,这意味着 objTwoobjOne 之前被处理。This runs in reverse order from which they were declared, meaning that objTwo is disposed before objOne.

无法接受的模式Unacceptable pattern

如果从 AnotherAsyncDisposable 构造函数引发异常,则 objOne 不会得到正确处理:If an exception is thrown from the AnotherAsyncDisposable constructor, then objOne does not get properly disposed:

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))
        {
            // Only objOne has its DisposeAsync called.
        }

        Console.ReadLine();
    }
}

提示

避免此模式,因为它可能导致意外行为。Avoid this pattern as it could lead to unexpected behavior.

请参阅See also