Asynchronní návratové typy (C#)

Asynchronní metody můžou mít následující návratové typy:

Další informace o asynchronních metodách najdete v tématu Asynchronní programování s asynchronním a awaitm (C#).

Existuje také několik dalších typů, které jsou specifické pro úlohy Windows:

Návratový typ úkolu

Asynchronní metody, které neobsahují return příkaz nebo obsahují return příkaz, který nevrací operand, obvykle mají návratový Tasktyp . Tyto metody se vrátí void , pokud běží synchronně. Pokud použijete návratový Task typ pro asynchronní metodu, volající metoda může pomocí await operátoru pozastavit dokončení volajícího, dokud se zavolá asynchronní metoda nedokončí.

V následujícím příkladu WaitAndApologizeAsync metoda neobsahuje return příkaz, takže metoda vrátí Task objekt. Vrácení možnostiWaitAndApologizeAsync, která Task se má očekávat. Typ Task neobsahuje Result vlastnost, protože nemá žádnou návratovou hodnotu.

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 je očekávána pomocí příkazu await místo výrazu await, podobně jako volání příkazu synchronní void-returning metoda. Aplikace operátoru await v tomto případě negeneruje hodnotu. Pokud je pravý operand , awaitTask<TResult>await výraz vytvoří výsledek .T Když je pravý operand , awaitTaskawait a jeho operand jsou příkaz.

Volání WaitAndApologizeAsync můžete oddělit od aplikace operátoru await, jak ukazuje následující kód. Mějte však na paměti, že Task vlastnost nemá Result a že se při použití operátoru Taskawait na operátoru operátoru await negeneruje žádná hodnota .

Následující kód odděluje volání WaitAndApologizeAsync metody od čekání na úkol, který metoda vrátí.

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

Návratový typ TResult> úkolu<

Návratový Task<TResult> typ se používá pro asynchronní metodu, která obsahuje návratovýpříkaz, ve kterém je TResultoperand .

V následujícím příkladu GetLeisureHoursAsync metoda obsahuje return příkaz, který vrací celé číslo. Deklarace metody musí určovat návratový Task<int>typ . Asynchronní FromResult metoda je zástupný symbol pro operaci, která vrací 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

Když GetLeisureHoursAsync je volána z výrazu await v ShowTodaysInfo metodě, výraz await načte celočíselnou hodnotu (hodnotu ) leisureHoursuloženou v úkolu vrácené metodou GetLeisureHours . Další informace o výrazech await najdete v tématu await.

Lépe se dozvíte, jak načte výsledek tím, že await oddělíte volání GetLeisureHoursAsync od aplikace await, jak ukazuje následující Task<T> kód. Volání metody GetLeisureHoursAsync , která není okamžitě očekávána, vrátí Task<int>hodnotu , jak byste očekávali od deklarace metody. Úkol je přiřazen k getLeisureHoursTask proměnné v příkladu. Protože getLeisureHoursTask je , Task<TResult>obsahuje Result vlastnost typu TResult. V tomto případě TResult představuje celočíselnou hodnotu. Při await použití na getLeisureHoursTaskvýraz await vyhodnotí obsah Result vlastnosti getLeisureHoursTask. Tato hodnota je přiřazena ret proměnné.

Důležité

Vlastnost Result je blokující vlastnost. Pokud se ho pokusíte získat přístup před dokončením jeho úkolu, vlákno, které je aktuálně aktivní, se zablokuje, dokud se úkol nedokončí a hodnota bude k dispozici. Ve většině případů byste k hodnotě měli přistupovat místo await přímého přístupu k vlastnosti.

Předchozí příklad načetl hodnotu Result vlastnosti, která zablokovala hlavní vlákno, aby Main metoda mohla vytisknout message konzolu před ukončením aplikace.

var getLeisureHoursTask = GetLeisureHoursAsync();

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

Console.WriteLine(message);

Návratový typ Void

Návratový void typ použijete v asynchronních obslužných rutinách událostí, které vyžadují návratový void typ. U metod jiných než obslužných rutin událostí, které nevrací hodnotu, byste měli vrátit Task místo toho, protože asynchronní metoda, která vrací void , nelze očekávat. Každý volající takové metody musí pokračovat v dokončení, aniž by čekal na dokončení volanou asynchronní metodu. Volající musí být nezávislý na všech hodnotách nebo výjimkách, které asynchronní metoda generuje.

Volající asynchronní metody void-return nemůže zachytit výjimky vyvolané metodou. Takové neošetřené výjimky pravděpodobně způsobí selhání vaší aplikace. Pokud metoda, která vrací Task výjimku nebo Task<TResult> vyvolá výjimku, je výjimka uložena ve vrácené úloze. Pokud je úkol očekávána, výjimka se znovu zřetědí. Ujistěte se, že všechny asynchronní metody, které mohou vytvořit výjimku, mají návratový Task typ nebo Task<TResult> že volání metody jsou očekávána.

Následující příklad ukazuje chování asynchronní obslužné rutiny události. V ukázkovém kódu musí obslužná rutina asynchronní události dát hlavnímu vláknu vědět, až se dokončí. Hlavní vlákno pak může počkat na dokončení obslužné rutiny asynchronní události před ukončením programu.

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.

Generalizované asynchronní návratové typy a HodnotaTask<TResult>

Asynchronní metoda může vrátit libovolný typ, který má přístupnou GetAwaiter metodu, která vrací instanci typu awaiter. Kromě toho typ vrácený z GetAwaiter metody musí mít System.Runtime.CompilerServices.AsyncMethodBuilderAttribute atribut. Další informace najdete v článku o atributech přečtených kompilátorem nebo specifikací jazyka C# pro vzor tvůrce typů úloh.

Tato funkce je doplňkem k čekaným výrazům, které popisují požadavky operandu await. Generalizované asynchronní návratové typy umožňují kompilátoru generovat async metody, které vracejí různé typy. Obecná vylepšení výkonu asynchronních návratových typů s povoleným výkonem v knihovnách .NET Vzhledem k tomu, že Task se Task<TResult> jedná o referenční typy, přidělení paměti v kritických cestách výkonu, zejména v případě, že přidělení probíhají v těsné smyčce, může nepříznivě ovlivnit výkon. Podpora zobecněných návratových typů znamená, že místo referenčního typu můžete vrátit jednoduchý typ hodnoty, abyste se vyhnuli dalším přidělením paměti.

.NET poskytuje System.Threading.Tasks.ValueTask<TResult> strukturu jako jednoduchou implementaci generalizované hodnoty vracející úlohu. Následující příklad používá ValueTask<TResult> strukturu k načtení hodnoty dvou hodů kostky.

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

Zápis generalizovaného asynchronního návratového typu je pokročilý scénář a je určený pro použití ve specializovaných prostředích. Zvažte použití Task, Task<T>a ValueTask<T> typy místo toho, které pokrývají většinu scénářů pro asynchronní kód.

V jazyce C# 10 a novějším můžete použít AsyncMethodBuilder atribut na asynchronní metodu (místo deklarace asynchronního návratového typu) a přepsat tvůrce pro tento typ. Tento atribut byste obvykle použili pro použití jiného tvůrce zadaného v modulu runtime .NET.

Asynchronní streamy s IAsyncEnumerable<T>

Asynchronní metoda může vrátit asynchronní stream reprezentovaný IAsyncEnumerable<T>. Asynchronní datový proud poskytuje způsob, jak vytvořit výčet položek načtených z datového proudu, když jsou prvky generovány v blocích s opakovanými asynchronními voláními. Následující příklad ukazuje asynchronní metodu, která generuje asynchronní stream:

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

Předchozí příklad načte řádky z řetězce asynchronně. Po přečtení každého řádku kód vyčíslí každé slovo v řetězci. Volající by vyčíslily každé slovo pomocí příkazu await foreach . Metoda čeká, když potřebuje asynchronně přečíst další řádek ze zdrojového řetězce.

Viz také