Uwaga
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Metody asynchroniczne mogą mieć następujące typy zwracanych danych.
- 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ęGetAwaiter
musi zaimplementować interfejs System.Runtime.CompilerServices.ICriticalNotifyCompletion. - IAsyncEnumerable<T>, dla metody asynchronicznej zwracającej strumień asynchroniczny.
Aby uzyskać więcej informacji na temat metod asynchronicznych, zobacz Asynchroniczne programowanie z użyciem async i await (C#).
Istnieje również kilka innych typów specyficznych dla obciążeń systemu Windows:
- DispatcherOperation, w przypadku operacji asynchronicznych ograniczonych do systemu Windows.
- IAsyncAction, w przypadku akcji asynchronicznych w aplikacjach platformy uniwersalnej systemu Windows (UWP), które nie zwracają wartości.
- IAsyncActionWithProgress<TProgress>, w przypadku akcji asynchronicznych w aplikacjach platformy UWP, które zgłaszają postęp, ale nie zwracają wartości.
- IAsyncOperation<TResult>, w przypadku operacji asynchronicznych w aplikacjach platformy UWP, które zwracają wartość.
- IAsyncOperationWithProgress<TResult,TProgress>, dla operacji asynchronicznych w aplikacjach platformy UWP, które zgłaszają postęp i zwracają wartość.
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 zwracają void
, jeśli działają synchronicznie. Jeśli używasz typu zwrotnego Task dla metody asynchronicznej, metoda wywołująca może użyć operatora await
do wstrzymania zakończenia, dopóki wywoływana metoda asynchroniczna się nie zakończy.
W poniższym przykładzie metoda WaitAndApologizeAsync
nie zawiera instrukcji return
, więc zwraca obiekt Task. Zwrócenie Task
umożliwia oczekiwanie na 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
jest oczekiwane za pomocą instrukcji await zamiast wyrażenia await, podobnie jak instrukcja wywołania dla metody synchronicznej zwracającej void. 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 await
to Task, await
i jego operand są instrukcją.
Wywołanie WaitAndApologizeAsync
można oddzielić od zastosowania operatora await, co pokazuje poniższy kod. Należy jednak pamiętać, że Task
nie ma właściwości Result
i że żadna wartość nie jest generowana, gdy operator await jest stosowany do 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 zadania<TResult>
Typ zwracany Task<TResult> jest używany dla metody asynchronicznej, która zawiera instrukcję return, w której operand to TResult
.
W poniższym przykładzie metoda GetLeisureHoursAsync
zawiera instrukcję return
zwracającą liczbę całkowitą. Deklaracja metody musi określać typ zwracany .Task<int>
Metoda FromResult asynchroniczna jest symbolem zastępczym dla operacji zwracającej 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
Gdy GetLeisureHoursAsync
jest wywoływana z wewnątrz wyrażenia await w metodzie ShowTodaysInfo
, 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 wartość Task<int>
, co jest zgodne z deklaracją metody. Zadanie jest przypisywane do zmiennej getLeisureHoursTask
w przykładzie. Ponieważ getLeisureHoursTask
jest Task<TResult>, posiada właściwość Result typu TResult
. W tym przypadku TResult
reprezentuje typ liczby całkowitej. Gdy await
jest stosowany do getLeisureHoursTask
elementu , 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ć do niego dostęp przed zakończeniem zadania, wątek, który jest aktualnie aktywny, zostanie zablokowany, aż zadanie zostanie zakończone i wartość stanie się dostępna. 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, tak żeby metoda Main
mogła wyświetlić message
na 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 zwracany void
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 zamiast tego zwrócić Task, ponieważ metody asynchroniczne zwracające void
nie mogą być oczekiwane. Każdy obiekt wywołujący taką metodę musi kontynuować działanie aż do zakończenia, bez oczekiwania na zakończenie wywoływanej metody asynchronicznej. Obiekt wywołujący musi być niezależny od wartości i wyjątków generowanych przez metodę asynchroniczną.
Wywołujący metody asynchronicznej, która zwraca void, nie może przechwytywać wyjątków rzucanych przez tę metodę. Takie nieobsługiwane wyjątki mogą spowodować niepowodzenie aplikacji. Jeśli metoda, która zwraca Task lub Task<TResult>, zgłasza wyjątek, wyjątek ten jest przechowywany w zwracanym zadaniu. Wyjątek jest zmieniany, gdy zadanie jest oczekiwane. Upewnij się, że każda metoda asynchroniczna, która może wygenerować wyjątek, ma typ zwracany Task lub Task<TResult> oraz że wszystkie wywołania tej metody są oczekiwane.
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.
Zunifikowane 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 jest uzupełnieniem dla wyrażeń oczekujących, które określają wymagania dla 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
Tworzenie uogólnionego typu zwrotnego dla operacji asynchronicznych jest zaawansowanym scenariuszem, przeznaczonym do użycia w dedykowanych środowiskach. Rozważ użycie typów Task
, Task<T>
i ValueTask<T>
, które obejmują większość scenariuszy dla kodu asynchronicznego zamiast tego.
Atrybut AsyncMethodBuilder
można zastosować do metody asynchronicznej (zamiast deklaracji typu zwrotnego asynchronicznego), aby zastąpić konstruktora 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 asynchroniczna może zwracać strumień asynchroniczny, reprezentowany przez IAsyncEnumerable<T>. 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. Dzwoniący 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.