Asynchronní návratové typy (C#)
Asynchronní metody můžou 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.- Libovolný typ, který má přístupnou
GetAwaiter
metodu. Objekt vrácený metodouGetAwaiter
musí implementovat System.Runtime.CompilerServices.ICriticalNotifyCompletion rozhraní. - IAsyncEnumerable<T>, pro asynchronní metodu, která vrací asynchronní datový proud.
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:
- DispatcherOperation, pro asynchronní operace omezené na Windows.
- IAsyncAction– pro asynchronní akce v UPW, které nevrací hodnotu.
- IAsyncActionWithProgress<TProgress>– pro asynchronní akce v UPW, které hlásí průběh, ale nevrací hodnotu.
- IAsyncOperation<TResult>, pro asynchronní operace v UPW, které vracejí hodnotu.
- IAsyncOperationWithProgress<TResult,TProgress>– pro asynchronní operace v UPW, které hlásí průběh a vrací hodnotu.
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 , await
Task<TResult>await
výraz vytvoří výsledek .T
Když je pravý operand , await
Taskawait
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 Task
await 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 TResult
operand .
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 ) leisureHours
ulož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 getLeisureHoursTask
vý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.