Typy zwracane asynchroniczne (C#)

Metody asynchroniczne mogą mieć następujące typy zwracane:

  • Task, dla metody asynchronicznej, która wykonuje operację, ale nie zwraca żadnej wartości.
  • Task<TResult>, dla metody asynchronicznej, która zwraca wartość.
  • void, dla programu obsługi zdarzeń.
  • Dowolny typ, który ma dostępną GetAwaiter metodę. Obiekt zwrócony przez metodę GetAwaiterSystem.Runtime.CompilerServices.ICriticalNotifyCompletion musi zaimplementować interfejs.
  • IAsyncEnumerable<T>, dla metody asynchronicznej zwracającej strumień asynchroniczny.

Aby uzyskać więcej informacji na temat metod asynchronicznych, zobacz Asynchroniczne programowanie za pomocą asynchronicznego i await (C#).

Istnieje również kilka innych typów specyficznych dla obciążeń systemu Windows:

Typ zwracania zadania

Metody asynchroniczne, które nie zawierają return instrukcji lub które zawierają instrukcję return , która nie zwraca operandu, zwykle ma zwracany typ Task. Takie metody są zwracane void , jeśli są uruchamiane synchronicznie. Jeśli używasz typu zwracanego Task dla metody asynchronicznej, metoda wywołująca może użyć await operatora do wstrzymania ukończenia obiektu wywołującego do momentu zakończenia wywoływanej metody asynchronicznej.

W poniższym przykładzie WaitAndApologizeAsync metoda nie zawiera return instrukcji, więc metoda zwraca Task obiekt. Zwracanie Task opcji umożliwia oczekiwanie WaitAndApologizeAsync . Typ Task nie zawiera Result właściwości, ponieważ nie ma zwracanej wartości.

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 metoda jest oczekiwana przy użyciu instrukcji await zamiast wyrażenia await, podobnie jak instrukcja wywołująca dla metody synchronicznej zwracanej przez pustkę. Zastosowanie operatora await w tym przypadku nie generuje wartości. Gdy prawy operand elementu await to Task<TResult>, await wyrażenie generuje wynik T. Gdy prawy operand elementu awaitTaskto , await argument i jego operand są instrukcją.

Wywołanie metody WaitAndApologizeAsync można oddzielić od aplikacji operatora await, jak pokazano w poniższym kodzie. Należy jednak pamiętać, że obiekt Task nie ma Result właściwości i że żadna wartość nie jest generowany, gdy operator await jest stosowany do obiektu Task.

Poniższy kod oddziela wywołanie WaitAndApologizeAsync metody przed oczekiwaniem na zadanie zwracane przez metodę.

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

Typ zwracany TResult> zadania<

Zwracany Task<TResult> typ jest używany dla metody asynchronicznej, która zawiera instrukcję return , w której operand to TResult.

W poniższym przykładzie metoda zawiera instrukcję zwracającą GetLeisureHoursAsyncreturn liczbę całkowitą. Deklaracja metody musi określać typ zwracany .Task<int> Metoda FromResult asynchronizna jest symbolem zastępczym dla operacji zwracającej DayOfWeekelement .

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

Gdy GetLeisureHoursAsync jest wywoływana z wewnątrz wyrażenia await w ShowTodaysInfo metodzie, wyrażenie await pobiera wartość całkowitą (wartość leisureHours) przechowywaną w zadaniu zwróconym przez metodę GetLeisureHours . Aby uzyskać więcej informacji na temat wyrażeń await, zobacz await.

Lepiej zrozumieć, jak await pobiera wynik z obiektu Task<T> , oddzielając wywołanie metody GetLeisureHoursAsync od aplikacji await, jak pokazano w poniższym kodzie. Wywołanie metody GetLeisureHoursAsync , która nie jest natychmiast oczekiwana, zwraca metodę Task<int>, jak można oczekiwać od deklaracji metody. Zadanie jest przypisywane do zmiennej getLeisureHoursTask w przykładzie. Ponieważ getLeisureHoursTask jest elementem Task<TResult>, zawiera Result właściwość typu TResult. W tym przypadku TResult reprezentuje typ liczby całkowitej. Gdy await jest stosowany do getLeisureHoursTaskelementu , wyrażenie await oblicza zawartość Result właściwości getLeisureHoursTask. Wartość jest przypisywana do zmiennej ret .

Ważne

Właściwość Result jest właściwością blokującą. Jeśli spróbujesz uzyskać dostęp do niego przed zakończeniem jego zadania, wątek, który jest obecnie aktywny, zostanie zablokowany do momentu zakończenia zadania i będzie dostępna wartość. W większości przypadków należy uzyskać dostęp do wartości przy użyciu metody await zamiast bezpośrednio uzyskiwać dostęp do właściwości.

Poprzedni przykład pobrał wartość Result właściwości, aby zablokować główny wątek, aby Main metoda mogła wydrukować message element w konsoli przed zakończeniem aplikacji.

var getLeisureHoursTask = GetLeisureHoursAsync();

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

Console.WriteLine(message);

Typ zwracania pustki

Zwracany void typ jest używany w asynchronicznych programach obsługi zdarzeń, które wymagają zwracanego void typu. W przypadku metod innych niż programy obsługi zdarzeń, które nie zwracają wartości, należy zwrócić Task wartość, ponieważ nie można oczekiwać metody asynchronicznej, która zwraca void . Każdy obiekt wywołujący taką metodę musi kontynuować uzupełnianie bez oczekiwania na zakończenie wywoływanej metody asynchronicznej. Obiekt wywołujący musi być niezależny od wszelkich wartości lub wyjątków generowanych przez metodę asynchroniową.

Obiekt wywołujący metody asynchronicznej zwracającej wartość void nie może przechwytywać wyjątków zgłaszanych z metody . Takie nieobsługiwane wyjątki mogą spowodować niepowodzenie aplikacji. Jeśli metoda zwracająca Task wyjątek lub Task<TResult> zgłasza wyjątek, wyjątek jest przechowywany w zwracanym zadaniu. Wyjątek jest zmieniany, gdy zadanie jest oczekiwane. Upewnij się, że każda metoda asynchronizna, która może wygenerować wyjątek, ma typ Task zwracany lub Task<TResult> , że oczekuje się wywołań metody .

W poniższym przykładzie pokazano zachowanie asynchronicznego programu obsługi zdarzeń. W przykładowym kodzie program obsługi zdarzeń asynchronicznych musi poinformować główny wątek o zakończeniu. Następnie główny wątek może poczekać na ukończenie programu obsługi zdarzeń asynchronicznych przed zamknięciem 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.

Uogólnione typy zwracane asynchroniczne i ValueTask<TResult>

Metoda asynchronizna może zwracać dowolny typ, który ma dostępną GetAwaiter metodę zwracającą wystąpienie typu awaiter. Ponadto typ zwracany z GetAwaiter metody musi mieć System.Runtime.CompilerServices.AsyncMethodBuilderAttribute atrybut . Więcej informacji można dowiedzieć się w artykule Dotyczącym atrybutów odczytanych przez kompilator lub specyfikację języka C# dla wzorca konstruktora typów zadań.

Ta funkcja stanowi uzupełnienie oczekujących wyrażeń, które opisują wymagania dotyczące operandu .await Uogólnione typy zwracane asynchroniczne umożliwiają kompilatorowi generowanie async metod, które zwracają różne typy. Uogólnione typy zwracane asynchroniczne umożliwiają ulepszenia wydajności w bibliotekach platformy .NET. Ponieważ Task i Task<TResult> są typami referencyjnymi, alokacja pamięci w ścieżkach o krytycznym znaczeniu dla wydajności, szczególnie gdy alokacje występują w ciasnych pętlach, mogą mieć negatywny wpływ na wydajność. Obsługa uogólnionych typów zwracanych oznacza, że można zwrócić lekki typ wartości zamiast typu odwołania, aby uniknąć dodatkowych alokacji pamięci.

Platforma .NET udostępnia System.Threading.Tasks.ValueTask<TResult> strukturę jako uproszczoną implementację uogólnionej wartości zwracanej przez zadanie. W poniższym przykładzie użyto ValueTask<TResult> struktury do pobrania wartości dwóch rzutów kostkami.

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

Pisanie uogólnionego typu zwrotnego asynchronicznego jest zaawansowanym scenariuszem i jest przeznaczone do użycia w wyspecjalizowanych środowiskach. Rozważ użycie Tasktypów , Task<T>i ValueTask<T> , które obejmują większość scenariuszy dla kodu asynchronicznego.

W języku C# 10 lub nowszym można zastosować AsyncMethodBuilder atrybut do metody asynchronicznej (zamiast asynchronicznej deklaracji typu zwracanego), aby zastąpić konstruktora dla tego typu. Zazwyczaj ten atrybut należy zastosować, aby użyć innego konstruktora udostępnionego w środowisku uruchomieniowym platformy .NET.

Strumienie asynchroniczne z funkcją IAsyncEnumerable<T>

Metoda asynchronizna może zwracać strumień asynchroniczny reprezentowany przez IAsyncEnumerable<T>element . Strumień asynchroniczny umożliwia wyliczanie elementów odczytywanych ze strumienia, gdy elementy są generowane we fragmentach z powtarzającymi się wywołaniami asynchronicznymi. W poniższym przykładzie przedstawiono metodę asynchroniową, która generuje strumień asynchroniczny:

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

Powyższy przykład odczytuje wiersze z ciągu asynchronicznie. Po odczytaniu każdego wiersza kod wylicza każde słowo w ciągu. Osoby wywołujące wyliczają każde słowo przy użyciu instrukcji await foreach . Metoda oczekuje, gdy musi asynchronicznie odczytać następny wiersz z ciągu źródłowego.

Zobacz też