Share via


Async-returtyper (C#)

Async-metoder kan ha följande returtyper:

Mer information om asynkrona metoder finns i Asynkron programmering med asynkron asynkron programmering med asynkron inväntning (C#).

Det finns också flera andra typer som är specifika för Windows-arbetsbelastningar:

Typ av aktivitetsretur

Asynkrona metoder som inte innehåller en return -instruktion eller som innehåller en return instruktion som inte returnerar en operand har vanligtvis en returtyp av Task. Sådana metoder returnerar void om de körs synkront. Om du använder en Task returtyp för en asynkron metod kan en anropande metod använda en await operator för att pausa uppringarens slutförande tills den anropade asynkronmetoden har slutförts.

I följande exempel WaitAndApologizeAsync innehåller metoden inte någon return -instruktion, så metoden returnerar ett Task objekt. Att returnera en Task gör det möjligt WaitAndApologizeAsync att vänta. Typen Task innehåller inte en Result egenskap eftersom den inte har något returvärde.

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 väntar med hjälp av en await-instruktion i stället för ett inväntningsuttryck, liknande anropsuttrycket för en synkron void-returning-metod. Programmet för en inväntningsoperator i det här fallet genererar inte något värde. När den högra operanden för en await är en Task<TResult>, await genererar uttrycket ett resultat av T. När den högra operanden för en await är en Task, await är och dess operande en instruktion.

Du kan separera anropet till WaitAndApologizeAsync från programmet för en await-operator, som följande kod visar. Kom dock ihåg att en Task inte har en Result egenskap och att inget värde skapas när en inväntningsoperator tillämpas på en Task.

Följande kod separerar anropet av WaitAndApologizeAsync metoden från att vänta på den uppgift som metoden returnerar.

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

Returtyp för uppgifts-TResult<>

Returtypen Task<TResult> används för en asynkron metod som innehåller en retursats där operand är TResult.

I följande exempel GetLeisureHoursAsync innehåller metoden en return -instruktion som returnerar ett heltal. Metoddeklarationen måste ange returtypen Task<int>. Metoden FromResult async är en platshållare för en åtgärd som returnerar en 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

När GetLeisureHoursAsync anropas inifrån ett await-uttryck i ShowTodaysInfo metoden hämtar await-uttrycket heltalsvärdet (värdet leisureHoursför ) som lagras i aktiviteten som returneras av GetLeisureHours metoden. Mer information om inväntningsuttryck finns i await.

Du kan bättre förstå hur await hämtar resultatet från en Task<T> genom att separera anropet till GetLeisureHoursAsync från programmet awaitför , som följande kod visar. Ett anrop till en metod GetLeisureHoursAsync som inte väntar omedelbart returnerar en Task<int>, som du kan förvänta dig av metodens deklaration. Uppgiften tilldelas variabeln getLeisureHoursTask i exemplet. Eftersom getLeisureHoursTask är en Task<TResult>innehåller den en Result egenskap av typen TResult. I det här fallet TResult representerar en heltalstyp. När await tillämpas på getLeisureHoursTaskutvärderas await-uttrycket till innehållet i Result egenskapen getLeisureHoursTaskför . Värdet tilldelas variabeln ret .

Viktigt!

Egenskapen Result är en blockerande egenskap. Om du försöker komma åt den innan uppgiften är klar blockeras tråden som är aktiv tills aktiviteten har slutförts och värdet är tillgängligt. I de flesta fall bör du komma åt värdet genom att använda await i stället för att komma åt egenskapen direkt.

I föregående exempel hämtades värdet Result för egenskapen för att blockera huvudtråden Main så att metoden kunde skriva ut message till konsolen innan programmet avslutades.

var getLeisureHoursTask = GetLeisureHoursAsync();

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

Console.WriteLine(message);

Returtyp för tomrum

Du använder void returtypen i asynkrona händelsehanterare, som kräver en void returtyp. För andra metoder än händelsehanterare som inte returnerar ett värde bör du returnera en Task i stället, eftersom det inte går att vänta på en asynkron metod som returnerar void . Alla anropare av en sådan metod måste fortsätta att slutföras utan att vänta på att den anropade async-metoden ska slutföras. Anroparen måste vara oberoende av alla värden eller undantag som asynkroniseringsmetoden genererar.

Anroparen för en asynkron asynkron metod som returneras kan inte fånga undantag som genereras från metoden. Sådana ohanterade undantag kommer sannolikt att leda till att programmet misslyckas. Om en metod som returnerar ett Task eller Task<TResult> utlöser ett undantag lagras undantaget i den returnerade aktiviteten. Undantaget ändras när aktiviteten väntar. Kontrollera att alla asynkrona metoder som kan skapa ett undantag har en returtyp av Task eller Task<TResult> och att anrop till metoden väntar.

I följande exempel visas beteendet för en asynkron händelsehanterare. I exempelkoden måste en asynkron händelsehanterare meddela huvudtråden när den är klar. Sedan kan huvudtråden vänta tills en asynkron händelsehanterare har slutförts innan programmet avslutas.

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.

Generaliserade asynkrona returtyper och ValueTask<TResult>

En asynkron metod kan returnera alla typer som har en tillgänglig GetAwaiter metod som returnerar en instans av en awaiter-typ. Dessutom måste den typ som returneras från GetAwaiter metoden ha attributet System.Runtime.CompilerServices.AsyncMethodBuilderAttribute . Du kan läsa mer i artikeln om attribut som lästs av kompilatorn eller C#-specifikationen för mönstret Aktivitetstypsbyggare.

Den här funktionen är ett komplement till väntande uttryck som beskriver kraven för operand i await. Med generaliserade asynkrona returtyper kan kompilatorn generera async metoder som returnerar olika typer. Generaliserade asynkrona returtyper har aktiverat prestandaförbättringar i .NET-biblioteken. Eftersom Task och Task<TResult> är referenstyper kan minnesallokering i prestandakritiska sökvägar, särskilt när allokeringar sker i snäva loopar, påverka prestanda negativt. Stöd för generaliserade returtyper innebär att du kan returnera en lätt värdetyp i stället för en referenstyp för att undvika ytterligare minnesallokeringar.

.NET tillhandahåller System.Threading.Tasks.ValueTask<TResult> strukturen som en enkel implementering av ett generaliserat värde för uppgiftsretur. I följande exempel används ValueTask<TResult> strukturen för att hämta värdet för två tärningskast.

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

Att skriva en generaliserad asynkron returtyp är ett avancerat scenario och är avsett för användning i specialiserade miljöer. Överväg att använda typerna Task, Task<T>och ValueTask<T> i stället, som täcker de flesta scenarier för asynkron kod.

I C# 10 och senare kan du tillämpa AsyncMethodBuilder attributet på en asynkron metod (i stället för deklarationen av asynkron returtyp) för att åsidosätta byggaren för den typen. Vanligtvis använder du det här attributet för att använda en annan byggare som tillhandahålls i .NET-körningen.

Asynkrona strömmar med IAsyncEnumerable<T>

En asynkron metod kan returnera en asynkron ström som representeras av IAsyncEnumerable<T>. En asynkron ström ger ett sätt att räkna upp objekt som läses från en ström när element genereras i segment med upprepade asynkrona anrop. I följande exempel visas en asynkron metod som genererar en asynkron ström:

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

Föregående exempel läser rader från en sträng asynkront. När varje rad har lästs räknar koden upp varje ord i strängen. Anropare skulle räkna upp varje ord med hjälp av -instruktionen await foreach . Metoden väntar när den asynkront behöver läsa nästa rad från källsträngen.

Se även