異步方法可以有下列傳回類型:
- Task,用於執行作業但未傳回任何值的異步方法。
- Task<TResult>,針對傳回值的異步方法。
-
void
事件處理程式。 - 具有可存取
GetAwaiter
方法的任何類型。GetAwaiter
方法傳回的對象必須實作 System.Runtime.CompilerServices.ICriticalNotifyCompletion 介面。 - IAsyncEnumerable<T>,這是傳回 異步數據流的異步方法。
如需有關 async 方法的詳細資訊,請參閱 使用 async 和 await 進行異步程式設計。
有幾種其他類型專門針對 Windows 工作負載。
- DispatcherOperation,適用於在 Windows 環境中有限制的異步操作。
- IAsyncAction,適用於通用 Windows 平臺 (UWP) 應用程式中不會傳回值的異步動作。
- IAsyncActionWithProgress<TProgress>,適用於回報進度但不會傳回值的 UWP 應用程式中的異步動作。
- IAsyncOperation<TResult>,用於 UWP 應用程式中傳回值的異步作業。
- IAsyncOperationWithProgress<TResult,TProgress>,適用於 UWP 應用程式中報告進度並傳回值的異步作業。
任務回傳類型
不包含 return
語句或包含不返回操作數的 return
語句的異步方法,通常會有 Task作為返回型別。 如果這類方法以同步方式執行,則會傳回 void
。 如果您對一個異步方法使用 Task 傳回型別,則呼叫該方法時可使用 await
運算符來暫時延遲呼叫方的完成狀態,直到呼叫的異步方法執行完畢。
在下列範例中,WaitAndApologizeAsync
方法不包含 return
語句,因此方法會傳回 Task 物件。 傳回 Task
使得 WaitAndApologizeAsync
可以被等待。
Task 類型不包含 Result
屬性,因為它沒有傳回值。
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
會使用 await 語句而不是 await 表達式來等待,類似於同步傳回 void 的方法的呼叫語句。 在此情況下,await 運算子的應用程式不會產生值。 當 await
的右操作數是 Task<TResult>時,await
表示式會產生 T
的結果。 當 await
的右操作數是 Task時,await
及其操作數是語法陳述。
您可以將對 WaitAndApologizeAsync
的呼叫與 await 運算子的應用分開,如下列程式碼所示。 不過,請記住,Task
沒有 Result
屬性,而且當 await 運算子套用至 Task
時,不會產生任何值。
下列程式代碼會將呼叫 WaitAndApologizeAsync
方法與等候方法傳回的工作分開。
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> 返回類型
Task<TResult> 傳回型別適用於包含 傳回 語句,且該語句的操作數為 TResult
的異步方法。
在下列範例中,GetLeisureHoursAsync
方法包含傳回整數的 return
語句。 方法宣告必須指定 Task<int>
的傳回型別。
FromResult 非同步方法是代表執行返回 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
從 GetLeisureHoursAsync
方法的 await 運算式內呼叫 ShowTodaysInfo
時,await 運算式會擷取存在 leisureHours
方法返回的任務中的整數值(即 GetLeisureHours
的值)。 如需 await 表達式的詳細資訊,請參閱 await。
您可以進一步瞭解 await
如何從 Task<T>
擷取結果,方法是將 GetLeisureHoursAsync
呼叫與 await
的應用程式區隔開,如下列程式代碼所示。 如果未立即等候的方法 GetLeisureHoursAsync
被呼叫,則會如同您所預期的從方法宣告中返回 Task<int>
。 任務被指派給範例中的 getLeisureHoursTask
變數。 因為 getLeisureHoursTask
是 Task<TResult>,所以它包含類型為 Result的 TResult
屬性。 在此情況下,TResult
代表整數類型。 當 await
套用至 getLeisureHoursTask
時,await 運算式會評估為 Result的 getLeisureHoursTask
屬性的內容。 值會指派給 ret
變數。
重要
Result 屬性是封鎖屬性。 如果您在工作完成之前嘗試存取它,則目前作用中的線程會遭到封鎖,直到工作完成且值可用為止。 在大部分情況下,您應該使用 await
來存取值,而不是直接存取 屬性。
上一個範例擷取了 Result 屬性的值,以封鎖主線程,讓 Main
方法可以在應用程式結束之前,將 message
列印到控制台。
var getLeisureHoursTask = GetLeisureHoursAsync();
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await getLeisureHoursTask}";
Console.WriteLine(message);
void 傳回類型
您可以在異步事件處理程式中使用 void
傳回型別,這需要 void
傳回型別。 對於未傳回值之事件處理程式以外的方法,您應該改為傳回 Task,因為無法等候傳回 void
的異步方法。 任何呼叫此類方法的呼叫者都必須繼續執行直到結束,而不需要等待所呼叫的異步方法完成。 呼叫端必須與異步方法產生的任何值或例外狀況無關。
返回 void 的非同步方法的呼叫者無法捕捉該方法擲出的例外。 這類未處理的例外狀況可能會導致您的應用程式失敗。 如果傳回 Task 或 Task<TResult> 的方法擲回例外狀況,則例外狀況會儲存在傳回的工作中。 當等待任務時,例外狀況會被重新拋出。 請確定任何可產生例外狀況的異步方法都有傳回類型為 Task 或 Task<TResult>,而且會等候對方法的呼叫。
下列範例顯示異步事件處理程序的行為。 在範例程式代碼中,異步事件處理程式必須在完成時讓主線程知道。 然後,主線程可以等候異步事件處理程式完成,再結束程式。
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.
一般化的非同步回傳類型和 ValueTask<TResult>
異步方法可以回傳任何類型,只要該類型具有可存取的 GetAwaiter
方法傳回的類型必須具有 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 屬性。 您可以在編譯程式
這個功能是對 可等待的表達式的互補,描述了 await
操作數的需求。 一般化異步傳回型別可讓編譯程式產生傳回不同類型的 async
方法。 泛型化異步返回類型允許在 .NET 類庫中改進效能。 由於 Task 和 Task<TResult> 是參考型別,因此效能關鍵路徑中的記憶體配置,特別是當配置在緊密迴圈中發生時,可能會對效能造成負面影響。 支援一般化傳回型別表示您可以傳回輕量型值型別,而不是參考型別,以避免更多的記憶體配置。
.NET 提供 System.Threading.Tasks.ValueTask<TResult> 結構,作為一般化任務傳回值的輕量化實作。 下列範例會使用 ValueTask<TResult> 結構來擷取兩個骰子卷的值。
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
撰寫泛用的異步傳回類型是進階應用場景,主要用於專用環境中。 請考慮改用 Task
、Task<T>
和 ValueTask<T>
類型,這涵蓋異步程序代碼的大部分案例。
您可以將 AsyncMethodBuilder
特性套用至非同步方法(而非非同步返回類型宣告)以覆寫該類型的生成器。 一般而言,您會套用這個屬性,以使用 .NET 執行階段中提供的不同建構器。
使用 IAsyncEnumerable<T> 的異步數據流
異步方法可能會傳回以 表示的 IAsyncEnumerable<T>。 異步數據流提供了一種方式,當元件以區塊形式並經由反覆的異步呼叫產生時,從數據流中讀取並列舉它們。 下列範例顯示產生異步數據流的異步方法:
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();
}
}
上述範例會用異步方式從字串中讀取行。 讀取每一行之後,程式代碼會列舉字串中的每個單字。 呼叫者會使用 await foreach
語句來列舉每個單詞。 方法會在需要從來源字串異步讀取下一行時等候。
另請參閱
- FromResult
- 在完成 時處理異步工作
- 使用 async 和 await 的非同步程式設計 (C#)
- 異步
- 等待