Asynchronní návratové typy (C#)
Asynchronní metody mohou mít následující návratové typy:
- Task, pro asynchronní metodu, která provádí operaci, ale nevrací žádnou hodnotu.
- Task<TResult>, pro asynchronní metodu, která vrací hodnotu.
void, pro obslužnou rutinu události.- Počínaje jazykem C# 7,0, jakýkoli typ, který má přístupnou
GetAwaitermetodu. Objekt vrácenýGetAwaitermetodou musí implementovat System.Runtime.CompilerServices.ICriticalNotifyCompletion rozhraní. - Počínaje jazykem C# 8,0, IAsyncEnumerable<T> pro asynchronní metodu, která vrací asynchronní datový proud.
Další informace o asynchronních metodách naleznete v tématu asynchronní programování s Async a await (C#).
existuje také několik dalších typů, které jsou specifické pro Windows úlohy:
- DispatcherOperation, pro asynchronní operace omezené na Windows.
- IAsyncAction, pro asynchronní akce v UWP, které nevracejí hodnotu.
- IAsyncActionWithProgress<TProgress>, pro asynchronní akce v UWP, které nastavují průběh, ale nevrací hodnotu.
- IAsyncOperation<TResult>pro asynchronní operace v UWP, které vracejí hodnotu.
- IAsyncOperationWithProgress<TResult,TProgress>, pro asynchronní operace v UWP, které nastavují průběh a vracejí hodnotu.
Návratový typ úlohy
Asynchronní metody, které neobsahují return příkaz nebo obsahují return příkaz, který nevrací operand, obvykle mají návratový typ Task . Tyto metody vrátí, void Pokud jsou spouštěny synchronně. Použijete-li Task návratový typ pro asynchronní metodu, volající metoda může použít await operátor pro pozastavení dokončování volajícího až do dokončení volané asynchronní metody.
V následujícím příkladu WaitAndApologizeAsync Metoda neobsahuje return příkaz, takže metoda vrátí Task objekt. Vrácení, Task WaitAndApologizeAsync které umožňuje očekávat. TaskTyp nezahrnuje vlastnost, Result 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án pomocí příkazu await namísto výrazu await, podobně jako volání příkazu pro synchronní metodu vracející typ void. Použití operátoru await v tomto případě nevytvoří hodnotu. Když je pravý operand operátoru await a Task<TResult> , await vygeneruje výsledek výrazu T . Když je pravý operand operátoru a await Task , await a jeho operand je příkaz.
Volání můžete oddělit WaitAndApologizeAsync od aplikace operátoru await, jak ukazuje následující kód. Mějte ale na paměti, že Task vlastnost neobsahuje Result vlastnost a že při použití operátoru await na objekt není vyprodukována žádná hodnota Task .
Následující kód odděluje volání WaitAndApologizeAsync metody z čekání na úlohu, kterou 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);
<TResult>Návratový typ úlohy
Task<TResult>Návratový typ se používá pro asynchronní metodu, která obsahuje příkaz return , ve kterém je operand TResult .
V následujícím příkladu GetLeisureHoursAsync Metoda obsahuje return příkaz, který vrací celé číslo. Deklarace metody musí určovat návratový typ Task<int> . FromResultAsynchronní 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 leisureHours ), která je uložena v úkolu vráceném GetLeisureHours metodou. Další informace o výrazech await naleznete v tématu await.
Můžete lépe pochopit, jak await načte výsledek z rozhraní Task<T> oddělením volání GetLeisureHoursAsync z aplikace await , jak ukazuje následující kód. Volání metody GetLeisureHoursAsync , která není okamžitě očekávána Task<int> , vrátí, 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 typ Integer. Při await použití na je getLeisureHoursTask Výraz Await vyhodnocen jako obsah Result vlastnosti getLeisureHoursTask . Hodnota je přiřazena ret proměnné.
Důležité
ResultVlastnost je vlastnost blokování. Pokud se pokusíte o přístup k tomuto úkolu před jeho dokončením, bude vlákno, které je aktuálně aktivní, blokováno, dokud se úloha nedokončí a hodnota nebude k dispozici. Ve většině případů byste měli k hodnotě přistupovat pomocí místo přímého await přístupu k vlastnosti.
Předchozí příklad obnovil hodnotu Result vlastnosti pro blokování hlavního vlákna tak, aby Main Metoda mohla vytisknout message do konzoly 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
Použijete void návratový typ v obslužných rutinách asynchronní události, které vyžadují void návratový typ. Pro jiné metody než obslužné rutiny událostí, které nevracejí hodnotu, byste měli Task místo toho vracet výjimku, protože asynchronní metoda, která vrací, void nemůže být očekávána. Jakýkoli volající takové metody musí pokračovat v dokončování bez čekání na dokončení volané asynchronní metody. Volající musí být nezávislý na všech hodnotách nebo výjimkách vygenerovaných asynchronní metodou.
Volající asynchronní metody vracející hodnotu void nemůže zachytit výjimky vyvolané z metody. Takovéto neošetřené výjimky pravděpodobně způsobí selhání aplikace. Pokud metoda, která vrátí Task nebo Task<TResult> vyvolá výjimku, je tato výjimka uložena v vráceném úkolu. Výjimka je znovu vyvolána, když je úloha očekávána. Ujistěte se, že všechny asynchronní metody, které mohou vyvolat výjimku, mají návratový typ Task nebo Task<TResult> a že volání metody jsou očekávána.
Další informace o tom, jak zachytit výjimky v asynchronních metodách, naleznete v části výjimky v asynchronních metodách v článku try-catch .
Následující příklad ukazuje chování obslužné rutiny asynchronní události. V ukázkovém kódu musí obslužná rutina asynchronní události dát hlavnímu vláknu po jeho dokončení informace. Pak hlavní vlákno může počkat na dokončení asynchronní obslužné rutiny události před ukončením programu.
using System;
using System.Threading.Tasks;
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 ValueTask<TResult>
Počínaje jazykem C# 7,0 může asynchronní metoda vracet libovolný typ, který má přístupnou GetAwaiter metodu, která vrací instanci typu await. 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í funkcí pro úlohy, jako jsou návratové typy.
Tato funkce je doplňkem k očekávaným výrazům, které popisují požadavky na operand await . Generalizované asynchronní návratové typy umožňují kompilátoru generovat async metody, které vracejí jiné typy. Generalizované asynchronní návratové typy s povolenými vylepšeními výkonu v knihovnách .NET. Vzhledem Task k tomu, že a Task<TResult> jsou typy odkazů, přidělování paměti v cestách kritických pro výkon, zejména v případě, že přidělení dochází v těsných smyčkách, může negativně ovlivnit výkon. Podpora zobecněných návratových typů znamená, že můžete místo typu odkazu vrátit typ s odlehčenou hodnotou, abyste zabránili dalšímu přidělení paměti.
Rozhraní .NET poskytuje System.Threading.Tasks.ValueTask<TResult> strukturu jako zjednodušenou implementaci generalizované hodnoty vracející úlohy. chcete-li použít System.Threading.Tasks.ValueTask<TResult> typ, je nutné přidat System.Threading.Tasks.Extensions balíček NuGet do projektu. V následujícím příkladu se pomocí ValueTask<TResult> struktury načte hodnota dvou indexů.
using System;
using System.Threading.Tasks;
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é asynchronní návratové typu je pokročilý scénář a je zaměřený na použití ve specializovaných prostředích. Zvažte Task Task<T> ValueTask<T> místo toho použití typů, a, které pokrývají většinu scénářů pro asynchronní kód.
V C# 10 a novějších můžete použít AsyncMethodBuilder atribut na asynchronní metodu (namísto deklarace asynchronního návratového typu) pro přepsání tvůrce pro daný typ. Obvykle byste tento atribut použili pro použití jiného tvůrce, který je k dispozici v modulu .NET Runtime.
Asynchronní streamování pomocí IAsyncEnumerable<T>
Počínaje jazykem C# 8,0 může asynchronní metoda vracet asynchronní datový proud reprezentovaný IAsyncEnumerable<T> . Asynchronní datový proud poskytuje způsob, jak vytvořit výčet položek č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í datový proud:
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 čte řádky z řetězce asynchronně. Po načtení každého řádku kód vytvoří výčet každého slova v řetězci. Volající by mohli vytvořit výčet každého slova pomocí await foreach příkazu. Metoda čeká v případě, kdy potřebuje asynchronní čtení dalšího řádku ze zdrojového řetězce.