Asynchrone retourtypen (C#)

Asynchrone methoden kunnen de volgende retourtypen hebben:

  • Task, voor een asynchrone methode die een bewerking uitvoert, maar geen waarde retourneert.
  • Task<TResult>, voor een asynchrone methode die een waarde retourneert.
  • void, voor een gebeurtenis-handler.
  • Elk type dat een toegankelijke GetAwaiter methode heeft. Het object dat door de GetAwaiter methode wordt geretourneerd, moet de System.Runtime.CompilerServices.ICriticalNotifyCompletion interface implementeren.
  • IAsyncEnumerable<T>, voor een asynchrone methode die een asynchrone stroom retourneert.

Zie Asynchrone programmering met asynchroon programmeren met asynchroon en wachten (C#) voor meer informatie over asynchrone methoden.

Er bestaan ook verschillende andere typen die specifiek zijn voor Windows-workloads:

Retourtype taak

Asynchrone methoden die geen instructie bevatten return of die een return instructie bevatten die geen operand retourneert, hebben meestal een retourtype Task. Dergelijke methoden retourneren void als ze synchroon worden uitgevoerd. Als u een Task retourtype gebruikt voor een asynchrone methode, kan een aanroepmethode een await operator gebruiken om de voltooiing van de aanroeper op te schorten totdat de aangeroepen asynchrone methode is voltooid.

In het volgende voorbeeld bevat return de WaitAndApologizeAsync methode geen instructie, dus retourneert de methode een Task object. Het retourneren van een Task mogelijkheid WaitAndApologizeAsync om te wachten. Het Task type bevat Result geen eigenschap omdat deze geen retourwaarde heeft.

public static async Task DisplayCurrentInfoAsync()
{
    await WaitAndApologizeAsync();

    Console.WriteLine($"Today is {DateTime.Now:D}");
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Console.WriteLine("The current temperature is 76 degrees.");
}

static async Task WaitAndApologizeAsync()
{
    await Task.Delay(2000);

    Console.WriteLine("Sorry for the delay...\n");
}
// Example output:
//    Sorry for the delay...
//
// Today is Monday, August 17, 2020
// The current time is 12:59:24.2183304
// The current temperature is 76 degrees.

WaitAndApologizeAsync wordt verwacht met behulp van een await-instructie in plaats van een await-expressie, vergelijkbaar met de aanroepende instructie voor een synchrone ongeldige retourmethode. De toepassing van een await-operator in dit geval produceert geen waarde. Wanneer de rechteroperand van een await is, Task<TResult>produceert de await expressie een resultaat van T. Wanneer de rechteroperand van een await een is Task, zijn de await en de operand een instructie.

U kunt de aanroep WaitAndApologizeAsync scheiden van de toepassing van een wachtoperator, zoals in de volgende code wordt weergegeven. Houd er echter rekening mee dat een Task eigenschap niet heeft Result en dat er geen waarde wordt geproduceerd wanneer een wachtoperator wordt toegepast op een Task.

Met de volgende code wordt het aanroepen van de WaitAndApologizeAsync methode gescheiden van het wachten op de taak die door de methode wordt geretourneerd.

Task waitAndApologizeTask = WaitAndApologizeAsync();

string output =
    $"Today is {DateTime.Now:D}\n" +
    $"The current time is {DateTime.Now.TimeOfDay:t}\n" +
    "The current temperature is 76 degrees.\n";

await waitAndApologizeTask;
Console.WriteLine(output);

Retourtype Taak-TResult<>

Het Task<TResult> retourtype wordt gebruikt voor een asynchrone methode die een retourinstructie bevat waarin de operand zich bevindt TResult.

In het volgende voorbeeld bevat de GetLeisureHoursAsync methode een return instructie die een geheel getal retourneert. De declaratie van de methode moet een retourtype opgeven.Task<int> De FromResult asynchrone methode is een tijdelijke aanduiding voor een bewerking die een DayOfWeek.

public static async Task ShowTodaysInfoAsync()
{
    string message =
        $"Today is {DateTime.Today:D}\n" +
        "Today's hours of leisure: " +
        $"{await GetLeisureHoursAsync()}";

    Console.WriteLine(message);
}

static async Task<int> GetLeisureHoursAsync()
{
    DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);

    int leisureHours =
        today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
        ? 16 : 5;

    return leisureHours;
}
// Example output:
//    Today is Wednesday, May 24, 2017
//    Today's hours of leisure: 5

Wanneer GetLeisureHoursAsync wordt aangeroepen vanuit een await-expressie in de ShowTodaysInfo methode, haalt de await-expressie de gehele waarde (de waarde van leisureHours) op die is opgeslagen in de taak die door de GetLeisureHours methode wordt geretourneerd. Zie await voor meer informatie over await-expressies.

U kunt beter begrijpen hoe await het resultaat wordt opgehaald uit een Task<T> door de aanroep te GetLeisureHoursAsync scheiden van de toepassing van await, zoals in de volgende code wordt weergegeven. Een aanroep naar de methode GetLeisureHoursAsync die niet onmiddellijk wordt gewacht, retourneert een Task<int>, zoals u zou verwachten uit de declaratie van de methode. De taak wordt toegewezen aan de getLeisureHoursTask variabele in het voorbeeld. Omdat getLeisureHoursTask is een Task<TResult>, het bevat een Result eigenschap van het type TResult. In dit geval TResult vertegenwoordigt u een geheel getaltype. Wanneer await deze wordt toegepast getLeisureHoursTaskop, wordt de await-expressie geëvalueerd op de inhoud van de Result eigenschap van getLeisureHoursTask. De waarde wordt toegewezen aan de ret variabele.

Belangrijk

De Result eigenschap is een blokkerende eigenschap. Als u deze probeert te openen voordat de taak is voltooid, wordt de thread die momenteel actief is geblokkeerd totdat de taak is voltooid en de waarde beschikbaar is. In de meeste gevallen moet u toegang krijgen tot de waarde door deze te gebruiken await in plaats van rechtstreeks toegang te krijgen tot de eigenschap.

In het vorige voorbeeld is de waarde van de Result eigenschap opgehaald om de hoofdthread te blokkeren, zodat de Main methode de message naar de console kan afdrukken voordat de toepassing is beëindigd.

var getLeisureHoursTask = GetLeisureHoursAsync();

string message =
    $"Today is {DateTime.Today:D}\n" +
    "Today's hours of leisure: " +
    $"{await getLeisureHoursTask}";

Console.WriteLine(message);

Ongeldig retourtype

U gebruikt het void retourtype in asynchrone gebeurtenis-handlers, waarvoor een void retourtype is vereist. Voor andere methoden dan gebeurtenis-handlers die geen waarde retourneren, moet u in plaats daarvan een Task waarde retourneren, omdat een asynchrone methode die wordt geretourneerd void , niet kan worden gewacht. Elke aanroeper van een dergelijke methode moet worden voltooid zonder te wachten totdat de aangeroepen asynchrone methode is voltooid. De aanroeper moet onafhankelijk zijn van waarden of uitzonderingen die door de asynchrone methode worden gegenereerd.

De aanroeper van een asynchrone asynchrone methode kan geen uitzonderingen vangen die zijn gegenereerd door de methode. Dergelijke niet-verwerkte uitzonderingen leiden waarschijnlijk tot een storing in uw toepassing. Als een methode die een Task uitzondering retourneert of Task<TResult> genereert, wordt de uitzondering opgeslagen in de geretourneerde taak. De uitzondering wordt opnieuw uitgevoerd wanneer de taak wordt verwacht. Zorg ervoor dat elke asynchrone methode die een uitzondering kan produceren, een retourtype Task heeft of Task<TResult> dat aanroepen naar de methode worden verwacht.

In het volgende voorbeeld ziet u het gedrag van een asynchrone gebeurtenis-handler. In de voorbeeldcode moet een asynchrone gebeurtenishandler de hoofdthread laten weten wanneer deze is voltooid. Vervolgens kan de hoofdthread wachten totdat een asynchrone gebeurtenis-handler is voltooid voordat het programma wordt afgesloten.

public class NaiveButton
{
    public event EventHandler? Clicked;

    public void Click()
    {
        Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
        Clicked?.Invoke(this, EventArgs.Empty);
        Console.WriteLine("All listeners are notified.");
    }
}

public class AsyncVoidExample
{
    static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();

    public static async Task MultipleEventHandlersAsync()
    {
        Task<bool> secondHandlerFinished = s_tcs.Task;

        var button = new NaiveButton();

        button.Clicked += OnButtonClicked1;
        button.Clicked += OnButtonClicked2Async;
        button.Clicked += OnButtonClicked3;

        Console.WriteLine("Before button.Click() is called...");
        button.Click();
        Console.WriteLine("After button.Click() is called...");

        await secondHandlerFinished;
    }

    private static void OnButtonClicked1(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 1 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 1 is done.");
    }

    private static async void OnButtonClicked2Async(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 2 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 2 is about to go async...");
        await Task.Delay(500);
        Console.WriteLine("   Handler 2 is done.");
        s_tcs.SetResult(true);
    }

    private static void OnButtonClicked3(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 3 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 3 is done.");
    }
}
// Example output:
//
// Before button.Click() is called...
// Somebody has clicked a button. Let's raise the event...
//    Handler 1 is starting...
//    Handler 1 is done.
//    Handler 2 is starting...
//    Handler 2 is about to go async...
//    Handler 3 is starting...
//    Handler 3 is done.
// All listeners are notified.
// After button.Click() is called...
//    Handler 2 is done.

Gegeneraliseerde asynchrone retourtypen en ValueTask<TResult>

Een asynchrone methode kan elk type retourneren dat een toegankelijke GetAwaiter methode heeft die een exemplaar van een wachtertype retourneert. Daarnaast moet het type dat wordt geretourneerd uit de GetAwaiter methode het System.Runtime.CompilerServices.AsyncMethodBuilderAttribute kenmerk hebben. Meer informatie vindt u in het artikel over kenmerken die worden gelezen door de compiler of de C#-specificatie voor het patroon Opbouwfunctie voor taaktypen.

Deze functie is de aanvulling op te wachten expressies, waarin de vereisten voor de operand van await. Met gegeneraliseerde asynchrone retourtypen kan de compiler methoden genereren async die verschillende typen retourneren. Gegeneraliseerde asynchrone retourtypen zijn prestatieverbeteringen ingeschakeld in de .NET-bibliotheken. Omdat Task en Task<TResult> verwijzingstypen zijn, kan geheugentoewijzing in prestatiekritieke paden, met name wanneer toewijzingen voorkomen in strakke lussen, de prestaties nadelig beïnvloeden. Ondersteuning voor gegeneraliseerde retourtypen betekent dat u een lichtgewicht waardetype kunt retourneren in plaats van een verwijzingstype om extra geheugentoewijzingen te voorkomen.

.NET biedt de System.Threading.Tasks.ValueTask<TResult> structuur als een lichtgewicht implementatie van een gegeneraliseerde waarde voor het retourneren van taken. In het volgende voorbeeld wordt de ValueTask<TResult> structuur gebruikt om de waarde van twee dobbelstenen op te halen.

class Program
{
    static readonly Random s_rnd = new Random();

    static async Task Main() =>
        Console.WriteLine($"You rolled {await GetDiceRollAsync()}");

    static async ValueTask<int> GetDiceRollAsync()
    {
        Console.WriteLine("Shaking dice...");

        int roll1 = await RollAsync();
        int roll2 = await RollAsync();

        return roll1 + roll2;
    }

    static async ValueTask<int> RollAsync()
    {
        await Task.Delay(500);

        int diceRoll = s_rnd.Next(1, 7);
        return diceRoll;
    }
}
// Example output:
//    Shaking dice...
//    You rolled 8

Het schrijven van een gegeneraliseerd asynchroon retourtype is een geavanceerd scenario en is bedoeld voor gebruik in gespecialiseerde omgevingen. Overweeg in plaats daarvan de Task, Task<T>en ValueTask<T> typen te gebruiken, die betrekking hebben op de meeste scenario's voor asynchrone code.

In C# 10 en hoger kunt u het AsyncMethodBuilder kenmerk toepassen op een asynchrone methode (in plaats van de declaratie van het asynchrone retourtype) om de opbouwfunctie voor dat type te overschrijven. Normaal gesproken past u dit kenmerk toe om een andere opbouwfunctie te gebruiken die is opgegeven in de .NET-runtime.

Asynchrone streams met IAsyncEnumerable<T>

Een asynchrone methode kan een asynchrone stroom retourneren, vertegenwoordigd door IAsyncEnumerable<T>. Een asynchrone stroom biedt een manier om items op te sommen die uit een stroom worden gelezen wanneer elementen worden gegenereerd in segmenten met herhaalde asynchrone aanroepen. In het volgende voorbeeld ziet u een asynchrone methode waarmee een asynchrone stream wordt gegenereerd:

static async IAsyncEnumerable<string> ReadWordsFromStreamAsync()
{
    string data =
        @"This is a line of text.
              Here is the second line of text.
              And there is one more for good measure.
              Wait, that was the penultimate line.";

    using var readStream = new StringReader(data);

    string? line = await readStream.ReadLineAsync();
    while (line != null)
    {
        foreach (string word in line.Split(' ', StringSplitOptions.RemoveEmptyEntries))
        {
            yield return word;
        }

        line = await readStream.ReadLineAsync();
    }
}

In het voorgaande voorbeeld worden regels asynchroon van een tekenreeks gelezen. Zodra elke regel is gelezen, wordt elk woord in de tekenreeks opgesomd. Bellers zouden elk woord opsommen met behulp van de await foreach instructie. De methode wacht op het moment dat de volgende regel uit de brontekenreeks asynchroon moet worden gelezen.

Zie ook