Een Methode DisposeAsync implementeren

De System.IAsyncDisposable interface is geïntroduceerd als onderdeel van C# 8.0. U implementeert de IAsyncDisposable.DisposeAsync() methode wanneer u het opschonen van resources moet uitvoeren, net zoals bij het implementeren van een verwijderingsmethode. Een van de belangrijkste verschillen is echter dat deze implementatie asynchrone opschoonbewerkingen toestaat. De DisposeAsync() retourneert een ValueTask die de asynchrone verwijderingsbewerking vertegenwoordigt.

Het is gebruikelijk bij het implementeren van de IAsyncDisposable interface die klassen ook de IDisposable interface implementeren. Een goed implementatiepatroon van de IAsyncDisposable interface is om te worden voorbereid op synchrone of asynchrone verwijdering, maar het is geen vereiste. Als er geen synchrone wegwerp van uw klas mogelijk is, is het alleen IAsyncDisposable acceptabel. Alle richtlijnen voor het implementeren van het verwijderingspatroon zijn ook van toepassing op de asynchrone implementatie. In dit artikel wordt ervan uitgegaan dat u al bekend bent met het implementeren van een verwijderingsmethode.

Let op

Als u de IAsyncDisposable interface implementeert, maar niet de IDisposable interface, kan uw app mogelijk resources lekken. Als een klasse alleen aanroept, maar niet en een consument alleen aanroeptIAsyncDisposableDispose, wordt uw implementatie nooit aangeroepenDisposeAsync.IDisposable Dit zou resulteren in een bronlek.

Tip

Wat afhankelijkheidsinjectie betreft, wordt de levensduur van de service impliciet namens u beheerd bij het registreren van services in een IServiceCollectionservice. Het IServiceProvider en bijbehorende IHost opschonen van resources. Met name implementaties van IDisposable en IAsyncDisposable worden correct verwijderd aan het einde van hun opgegeven levensduur.

Zie Afhankelijkheidsinjectie in .NET voor meer informatie.

Verkennen DisposeAsync en DisposeAsyncCore methoden

De IAsyncDisposable interface declareert één methode zonder parameters, DisposeAsync(). Elke niet-verzegelde klasse moet een DisposeAsyncCore() methode definiëren die ook een ValueTask.

  • Een publicIAsyncDisposable.DisposeAsync() implementatie die geen parameters heeft.

  • Een protected virtual ValueTask DisposeAsyncCore() methode waarvan de handtekening is:

    protected virtual ValueTask DisposeAsyncCore()
    {
    }
    

De DisposeAsync methode

De public parameterloze DisposeAsync() methode wordt impliciet aangeroepen in een await using instructie en het doel ervan is om onbeheerde resources vrij te maken, algemene opschoning uit te voeren en aan te geven dat de finalizer, indien aanwezig, niet hoeft te worden uitgevoerd. Het vrijmaken van het geheugen dat is gekoppeld aan een beheerd object is altijd het domein van de garbagecollector. Daarom heeft het een standaard implementatie:

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

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

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

Notitie

Een primair verschil in het asynchrone verwijderingspatroon vergeleken met het verwijderingspatroon, is dat de aanroep van DisposeAsync() de Dispose(bool) overbelastingsmethode wordt gegeven false als argument. Bij het implementeren van de IDisposable.Dispose() methode wordt echter in plaats daarvan true doorgegeven. Dit zorgt voor functionele gelijkwaardigheid met het synchrone verwijderingspatroon en zorgt er verder voor dat finalizer-codepaden nog steeds worden aangeroepen. Met andere woorden, de DisposeAsyncCore() methode zal beheerde resources asynchroon verwijderen, dus u wilt ze niet ook synchroon verwijderen. Dispose(false) Bel daarom in plaats van Dispose(true).

De DisposeAsyncCore methode

De DisposeAsyncCore() methode is bedoeld om het asynchrone opschonen van beheerde resources of voor trapsgewijze aanroepen naar uit te DisposeAsync()voeren. Hiermee worden de algemene asynchrone opschoonbewerkingen ingekapseld wanneer een subklasse een basisklasse overgaat die een implementatie is van IAsyncDisposable. De DisposeAsyncCore() methode is virtual zodanig dat afgeleide klassen aangepaste opschoning kunnen definiëren in hun onderdrukkingen.

Tip

Als een implementatie IAsyncDisposable is sealed, is de DisposeAsyncCore() methode niet nodig en kan de asynchrone opschoonactie rechtstreeks in de IAsyncDisposable.DisposeAsync() methode worden uitgevoerd.

Het asynchrone verwijderingspatroon implementeren

Alle niet-verzegelde klassen moeten worden beschouwd als een potentiële basisklasse, omdat ze kunnen worden overgenomen. Als u het asynchrone verwijderingspatroon voor een mogelijke basisklasse implementeert, moet u de protected virtual ValueTask DisposeAsyncCore() methode opgeven. In sommige van de volgende voorbeelden wordt een NoopAsyncDisposable klasse gebruikt die als volgt is gedefinieerd:

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

Hier volgt een voorbeeld van een implementatie van het asynchrone verwijderingspatroon dat gebruikmaakt van het NoopAsyncDisposable type. Het type wordt geïmplementeerd DisposeAsync door te retourneren 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;
    }
}

In het voorgaande voorbeeld:

  • Dit ExampleAsyncDisposable is een niet-verzegelde klasse waarmee de IAsyncDisposable interface wordt geïmplementeerd.
  • Het bevat een privéveld IAsyncDisposable , _exampledat is geïnitialiseerd in de constructor.
  • De DisposeAsync methode delegeert de methode en roept GC.SuppressFinalize aan om de garbagecollector op de DisposeAsyncCore hoogte te stellen dat de finalizer niet hoeft te worden uitgevoerd.
  • Het bevat een DisposeAsyncCore() methode die de _example.DisposeAsync() methode aanroept en het veld instelt op null.
  • De DisposeAsyncCore() methode is virtual, waardoor subklassen deze kunnen overschrijven met aangepast gedrag.

Verzegeld patroon voor asynchroon verwijderen

Als uw implementatieklasse kan zijn sealed, kunt u het asynchrone verwijderingspatroon implementeren door de IAsyncDisposable.DisposeAsync() methode te overschrijven. In het volgende voorbeeld ziet u hoe u het asynchrone verwijderingspatroon voor een verzegelde klasse implementeert:

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

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

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

In het voorgaande voorbeeld:

  • Het SealedExampleAsyncDisposable is een verzegelde klasse die de IAsyncDisposable interface implementeert.
  • Het bevatde _example veld is readonly en wordt geïnitialiseerd in de constructor.
  • Met DisposeAsync de methode wordt de _example.DisposeAsync() methode aangeroepen, waarbij het patroon wordt geïmplementeerd via het veld (trapsgewijze verwijdering).

Zowel verwijderings- als asynchrone verwijderingspatronen implementeren

Mogelijk moet u zowel de als IAsyncDisposable de IDisposable interfaces implementeren, met name wanneer uw klassebereik exemplaren van deze implementaties bevat. Dit zorgt ervoor dat u oproepen op de juiste manier kunt opschonen. Hier volgt een voorbeeldklasse die beide interfaces implementeert en de juiste richtlijnen voor het opschonen demonstreert.

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

De IDisposable.Dispose() en IAsyncDisposable.DisposeAsync() implementaties zijn beide eenvoudige standaardcode.

In de Dispose(bool) overbelastingsmethode wordt het IDisposable exemplaar voorwaardelijk verwijderd als dat niet nullhet geval is. Het IAsyncDisposable exemplaar wordt gecast als IDisposable, en als het ook niet nullis , wordt het ook verwijderd. Beide exemplaren worden vervolgens toegewezen aan null.

Met de DisposeAsyncCore() methode wordt dezelfde logische benadering gevolgd. Als het IAsyncDisposable exemplaar niet nullis, wordt de aanroep naar DisposeAsync().ConfigureAwait(false) het exemplaar in afwachting van de aanroep verwacht. Als het IDisposable exemplaar ook een implementatie is, IAsyncDisposablewordt het ook asynchroon verwijderd. Beide exemplaren worden vervolgens toegewezen aan null.

Elke implementatie streeft ernaar om alle mogelijke wegwerpobjecten te verwijderen. Dit zorgt ervoor dat het opschonen trapsgewijs wordt uitgevoerd.

Asynchroon wegwerp gebruiken

Als u een object dat de IAsyncDisposable interface implementeert, correct wilt gebruiken, gebruikt u de await en het gebruik van trefwoorden samen. Bekijk het volgende voorbeeld, waarbij de ExampleAsyncDisposable klasse wordt geïnstantieerd en vervolgens verpakt in een await using instructie.

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

        Console.ReadLine();
    }
}

Belangrijk

Gebruik de ConfigureAwait(IAsyncDisposable, Boolean) extensiemethode van de IAsyncDisposable interface om te configureren hoe de voortzetting van de taak wordt ge marshalld op de oorspronkelijke context of scheduler. Zie De veelgestelde vragen over ConfigureAwait voor meer informatieConfigureAwait.

In situaties waarin het gebruik van ConfigureAwait niet nodig is, kan de await using instructie als volgt worden vereenvoudigd:

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

        Console.ReadLine();
    }
}

Bovendien kan het worden geschreven om het impliciete bereik van een gebruiksdeclaratie te gebruiken.

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

        // Interact with the exampleAsyncDisposable instance.

        Console.ReadLine();
    }
}

Meerdere wachtende trefwoorden in één regel

Soms kan het await trefwoord meerdere keren binnen één regel worden weergegeven. Denk bijvoorbeeld aan de volgende code:

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

In het voorgaande voorbeeld:

  • De BeginTransactionAsync methode is in afwachting.
  • Het retourtype is DbTransaction, dat implementeert IAsyncDisposable.
  • De transaction wordt asynchroon gebruikt en wordt ook gewacht.

Gestapeld gebruik

In situaties waarin u meerdere objecten maakt en gebruikt die worden geïmplementeerd IAsyncDisposable, is het mogelijk dat het stapelen van await using instructies met ConfigureAwait aanroepen DisposeAsync() in onjuiste omstandigheden kan voorkomen. Om ervoor te zorgen dat dit DisposeAsync() altijd wordt aangeroepen, moet u stapelen voorkomen. In de volgende drie codevoorbeelden ziet u acceptabele patronen die u kunt gebruiken.

Acceptabel patroon één


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

In het voorgaande voorbeeld wordt elke asynchrone opschoonbewerking expliciet onder het await using blok ingedeeld. Het buitenste bereik volgt hoe objOne de accolades, omsluiten objTwo, als zodanig objTwo eerst worden verwijderd, gevolgd door objOne. Beide IAsyncDisposable exemplaren hebben hun DisposeAsync() methode in afwachting, dus elke instantie voert de asynchrone opschoonbewerking uit. De aanroepen zijn genest, niet gestapeld.

Acceptabel patroon twee

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

In het voorgaande voorbeeld wordt elke asynchrone opschoonbewerking expliciet onder het await using blok ingedeeld. Aan het einde van elk blok heeft het bijbehorende IAsyncDisposable exemplaar zijn DisposeAsync() methode in afwachting, waardoor de asynchrone opschoonbewerking wordt uitgevoerd. De aanroepen zijn opeenvolgend, niet gestapeld. In dit scenario objOne wordt eerst verwijderd en vervolgens objTwo verwijderd.

Acceptabel patroon drie

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 het voorgaande voorbeeld is elke asynchrone opschoonbewerking impliciet afgestemd op de hoofdtekst van de methode. Aan het einde van het insluitblok voeren de IAsyncDisposable exemplaren hun asynchrone opschoonbewerkingen uit. Dit voorbeeld wordt uitgevoerd in omgekeerde volgorde van waaruit ze zijn gedeclareerd, wat betekent dat deze objTwo eerder objOnewordt verwijderd.

Onacceptabel patroon

De gemarkeerde regels in de volgende code laten zien wat het betekent om 'gestapeld gebruik' te hebben. Als er een uitzondering wordt gegenereerd vanuit de AnotherAsyncDisposable constructor, wordt geen van beide objecten correct verwijderd. De variabele objTwo wordt nooit toegewezen omdat de constructor niet is voltooid. Als gevolg hiervan is de constructor verantwoordelijk voor AnotherAsyncDisposable het verwijderen van resources die zijn toegewezen voordat er een uitzondering wordt gegenereerd. Als het ExampleAsyncDisposable type een finalizer heeft, komt het in aanmerking voor voltooien.

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

Vermijd dit patroon omdat dit kan leiden tot onverwacht gedrag. Als u een van de acceptabele patronen gebruikt, is het probleem van niet-gevormde objecten niet aanwezig. De opschoonbewerkingen worden correct uitgevoerd wanneer using instructies niet worden gestapeld.

Zie ook

Zie de Utf8JsonWriter broncode op GitHub voor een voorbeeld van een dubbele implementatie van IDisposable enIAsyncDisposable.