Implémenter une méthode DisposeAsyncImplement a DisposeAsync method

L' System.IAsyncDisposable interface a été introduite dans le cadre de C# 8,0.The System.IAsyncDisposable interface was introduced as part of C# 8.0. Vous implémentez la IAsyncDisposable.DisposeAsync() méthode lorsque vous devez effectuer un nettoyage des ressources, comme vous le feriez lors de l' implémentation d’une méthode dispose.You implement the IAsyncDisposable.DisposeAsync() method when you need to perform resource cleanup, just as you would when implementing a Dispose method. Toutefois, l’une des principales différences est que cette implémentation autorise les opérations de nettoyage asynchrones.One of the key differences however, is that this implementation allows for asynchronous cleanup operations. DisposeAsync()Retourne un ValueTask qui représente l’opération de suppression asynchrone.The DisposeAsync() returns a ValueTask that represents the asynchronous dispose operation.

C’est généralement le cas lors de l’implémentation de l' IAsyncDisposable interface que les classes implémentent également l' IDisposable interface.It is typical when implementing the IAsyncDisposable interface that classes will also implement the IDisposable interface. Un modèle d’implémentation correct de l' IAsyncDisposable interface doit être préparé pour une suppression synchrone ou asynchrone.A good implementation pattern of the IAsyncDisposable interface is to be prepared for either synchronous or asynchronous dispose. Toutes les instructions pour l’implémentation du modèle de suppression s’appliquent également à l’implémentation asynchrone.All of the guidance for implementing the dispose pattern also applies to the asynchronous implementation. Cet article suppose que vous êtes déjà familiarisé avec l’implémentation d' une méthode dispose.This article assumes that you're already familiar with how to implement a Dispose method.

DisposeAsync () et DisposeAsyncCore ()DisposeAsync() and DisposeAsyncCore()

L' IAsyncDisposable interface déclare une méthode sans paramètre unique, DisposeAsync() .The IAsyncDisposable interface declares a single parameterless method, DisposeAsync(). Toute classe non scellée doit avoir une DisposeAsyncCore() méthode supplémentaire qui retourne également un ValueTask .Any non-sealed class should have an additional DisposeAsyncCore() method that also returns a ValueTask.

  • public IAsyncDisposable.DisposeAsync() Implémentation qui n’a aucun paramètre.A public IAsyncDisposable.DisposeAsync() implementation that has no parameters.

  • Une protected virtual ValueTask DisposeAsyncCore() méthode dont la signature est :A protected virtual ValueTask DisposeAsyncCore() method whose signature is:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

Méthode DisposeAsync ()The DisposeAsync() method

La public méthode sans paramètre DisposeAsync() est appelée implicitement dans une await using instruction, et son objectif est de libérer des ressources non managées, d’effectuer un nettoyage général et d’indiquer que le finaliseur, s’il en existe un, ne doit pas être exécuté.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. La libération de la mémoire associée à un objet géré est toujours le domaine du garbage collector.Freeing the memory associated with a managed object is always the domain of the garbage collector. De ce fait, son implémentation standard est la suivante :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);
}

Notes

L’une des principales différences dans le modèle de suppression asynchrone par rapport au modèle de suppression est que l’appel de DisposeAsync() à la Dispose(bool) méthode de surcharge est donné false comme argument.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. Toutefois, lors de l’implémentation de la IDisposable.Dispose() méthode, true est passé.When implementing the IDisposable.Dispose() method, however, true is passed instead. Cela permet de garantir l’équivalence fonctionnelle avec le modèle de suppression synchrone et de s’assurer que les chemins de code du finaliseur sont toujours appelés.This helps ensure functional equivalence with the synchronous dispose pattern, and further ensures that finalizer code paths still get invoked. En d’autres termes, la DisposeAsyncCore() méthode supprime les ressources managées de manière asynchrone, donc vous ne voulez pas les supprimer de manière synchrone.In other words, the DisposeAsyncCore() method will dispose of managed resources asynchronously, so you don't want to dispose of them synchronously as well. Par conséquent, appelez Dispose(false) au lieu de Dispose(true) .Therefore, call Dispose(false) instead of Dispose(true).

Méthode DisposeAsyncCore ()The DisposeAsyncCore() method

La DisposeAsyncCore() méthode est conçue pour effectuer le nettoyage asynchrone des ressources managées ou pour les appels en cascade à DisposeAsync() .The DisposeAsyncCore() method is intended to perform the asynchronous cleanup of managed resources or for cascading calls to DisposeAsync(). Il encapsule les opérations de nettoyage asynchrone courantes lorsqu’une sous-classe hérite d’une classe de base qui est une implémentation de IAsyncDisposable .It encapsulates the common asynchronous cleanup operations when a subclass inherits a base class that is an implementation of IAsyncDisposable. La DisposeAsyncCore() méthode virtual permet aux classes dérivées de définir un nettoyage supplémentaire dans leurs substitutions.The DisposeAsyncCore() method is virtual so that derived classes can define additional cleanup in their overrides.

Conseil

Si une implémentation de IAsyncDisposable est sealed , la DisposeAsyncCore() méthode n’est pas nécessaire et le nettoyage asynchrone peut être effectué directement dans la IAsyncDisposable.DisposeAsync() méthode.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.

Implémenter le modèle de suppression asynchroneImplement the async dispose pattern

Toutes les classes non-sealed doivent être considérées comme une classe de base potentielle, car elles peuvent être héritées.All non-sealed classes should be considered a potential base class, because they could be inherited. Si vous implémentez le modèle de suppression Async pour une classe de base potentielle quelconque, vous devez fournir la protected virtual ValueTask DisposeAsyncCore() méthode.If you implement the async dispose pattern for any potential base class, you must provide the protected virtual ValueTask DisposeAsyncCore() method. Voici un exemple d’implémentation du modèle asynchrone dispose qui utilise un 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;
    }
}

L’exemple précédent utilise Utf8JsonWriter .The preceding example uses the Utf8JsonWriter. Pour plus d’informations sur System.Text.Json , voir How to migrate from Newtonsoft.Json to System.Text.Json.For more information about System.Text.Json, see How to migrate from Newtonsoft.Json to System.Text.Json.

Implémenter à la fois les modèles dispose et AsyncImplement both dispose and async dispose patterns

Vous devrez peut-être implémenter à la fois les IDisposable IAsyncDisposable interfaces et, en particulier lorsque votre portée de classe contient des instances de ces implémentations.You may need to implement both the IDisposable and IAsyncDisposable interfaces, especially when your class scope contains instances of these implementations. Cela vous permet de vous assurer que vous pouvez nettoyer correctement les appels en cascade.Doing so ensures that you can properly cascade clean up calls. Voici un exemple de classe qui implémente les deux interfaces et montre les recommandations appropriées pour le nettoyage.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;
        }
    }
}

Les IDisposable.Dispose() IAsyncDisposable.DisposeAsync() implémentations et sont à la fois du code réutilisable simple.The IDisposable.Dispose() and IAsyncDisposable.DisposeAsync() implementations are both simple boilerplate code.

Dans la Dispose(bool) méthode de surcharge, l' IDisposable instance est supprimée de manière conditionnelle, si ce n’est pas le cas null .In the Dispose(bool) overload method, the IDisposable instance is conditionally disposed of if it is not null. L' IAsyncDisposable instance est castée en IDisposable et, si ce n’est pas le cas null , elle est également supprimée.The IAsyncDisposable instance is cast as IDisposable, and if it is also not null, it is disposed of as well. Les deux instances sont ensuite affectées à null .Both instances are then assigned to null.

Avec la DisposeAsyncCore() méthode, la même approche logique est suivie.With the DisposeAsyncCore() method, the same logical approach is followed. Si l' IAsyncDisposable instance n’est pas null , son appel à DisposeAsync().ConfigureAwait(false) est attendu.If the IAsyncDisposable instance is not null, its call to DisposeAsync().ConfigureAwait(false) is awaited. Si l' IDisposable instance est également une implémentation de IAsyncDisposable , elle est également supprimée de manière asynchrone.If the IDisposable instance is also an implementation of IAsyncDisposable, it's also disposed of asynchronously. Les deux instances sont ensuite affectées à null .Both instances are then assigned to null.

Utilisation de Async jetableUsing async disposable

Pour utiliser correctement un objet qui implémente l' IAsyncDisposable interface, vous utilisez conjointement les mots clés await et using .To properly consume an object that implements the IAsyncDisposable interface, you use the await and using keywords together. Prenons l’exemple suivant, où la ExampleAsyncDisposable classe est instanciée puis encapsulée dans une await using instruction.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();
    }
}

Important

Utilisez la ConfigureAwait(IAsyncDisposable, Boolean) méthode d’extension de l' IAsyncDisposable interface pour configurer la façon dont la continuation de la tâche est marshalée sur son contexte ou planificateur d’origine.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. Pour plus d’informations sur ConfigureAwait , consultez le Forum aux questions ConfigureAwait.For more information on ConfigureAwait, see ConfigureAwait FAQ.

Dans les situations où l’utilisation de ConfigureAwait n’est pas nécessaire, l' await using instruction peut être simplifiée comme suit :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();
    }
}

En outre, il peut être écrit pour utiliser l’étendue implicite d’une déclaration 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();
    }
}

Utilisation empiléeStacked usings

Dans les situations où vous créez et utilisez plusieurs objets qui implémentent IAsyncDisposable , il est possible que l’empilement await using des instructions avec ConfigureAwait puisse empêcher les appels à DisposeAsync() dans des conditions errantes.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. Pour vous assurer que DisposeAsync() est toujours appelé, vous devez éviter l’empilement.To ensure that DisposeAsync() is always called, you should avoid stacking. Les trois exemples de code suivants illustrent des modèles acceptables à utiliser à la place.The following three code examples show acceptable patterns to use instead.

Modèle acceptable unAcceptable 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();
    }
}

Dans l’exemple précédent, chaque opération de nettoyage asynchrone est définie explicitement dans le await using bloc.In the preceding example, each asynchronous clean up operation is explicitly scoped under the await using block. La portée externe est définie par la manière dont objOne définit ses accolades, englobante objTwo , comme étant objTwo supprimées en premier, suivies de objOne .The outer scope is defined by how objOne sets its braces, enclosing objTwo, as such objTwo is disposed first, followed by objOne. Les deux IAsyncDisposable instances ont une DisposeAsync() méthode attendue, donc chaque instance exécute son opération de nettoyage asynchrone.Both IAsyncDisposable instances have their DisposeAsync() method awaited, so each instance performs its asynchronous clean up operation. Les appels sont imbriqués, et non empilés.The calls are nested, not stacked.

Modèle acceptable deuxAcceptable 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();
    }
}

Dans l’exemple précédent, chaque opération de nettoyage asynchrone est définie explicitement dans le await using bloc.In the preceding example, each asynchronous clean up operation is explicitly scoped under the await using block. À la fin de chaque bloc, la méthode de l' IAsyncDisposable instance correspondante est DisposeAsync() attendue, ce qui entraîne l’exécution de son opération de nettoyage asynchrone.At the end of each block, the corresponding IAsyncDisposable instance has its DisposeAsync() method awaited, thus performing its asynchronous clean up operation. Les appels sont séquentiels, et non empilés.The calls are sequential, not stacked. Dans ce scénario objOne est supprimé en premier, puis objTwo est supprimé.In this scenario objOne is disposed first, then objTwo is disposed.

Modèle acceptable troisAcceptable 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();
    }
}

Dans l’exemple précédent, chaque opération de nettoyage asynchrone est définie implicitement avec le corps de la méthode conteneur.In the preceding example, each asynchronous clean up operation is implicitly scoped with the containing method body. À la fin du bloc englobant, les IAsyncDisposable instances effectuent leurs opérations de nettoyage asynchrones.At the end of the enclosing block, the IAsyncDisposable instances perform their asynchronous clean up operations. Cette opération s’exécute dans l’ordre inverse à partir duquel elle a été déclarée, ce qui signifie qu’elle objTwo est supprimée avant objOne .This runs in reverse order from which they were declared, meaning that objTwo is disposed before objOne.

Modèle inacceptableUnacceptable pattern

Si une exception est levée à partir du AnotherAsyncDisposable constructeur, objOne n’est pas correctement supprimée :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();
    }
}

Conseil

Évitez ce modèle, car cela peut entraîner un comportement inattendu.Avoid this pattern as it could lead to unexpected behavior.

Voir aussiSee also

Pour obtenir un exemple d’implémentation double de IDisposable et IAsyncDisposable , consultez le Utf8JsonWriter code source sur GitHub.For a dual implementation example of IDisposable and IAsyncDisposable, see the Utf8JsonWriter source code on GitHub.