共用方式為


異步傳回型別 (C#)

異步方法可以有下列傳回類型:

如需有關 async 方法的詳細資訊,請參閱 使用 async 和 await 進行異步程式設計

有幾種其他類型專門針對 Windows 工作負載。

任務回傳類型

不包含 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 變數。 因為 getLeisureHoursTaskTask<TResult>,所以它包含類型為 ResultTResult 屬性。 在此情況下,TResult 代表整數類型。 當 await 套用至 getLeisureHoursTask時,await 運算式會評估為 ResultgetLeisureHoursTask 屬性的內容。 值會指派給 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 的非同步方法的呼叫者無法捕捉該方法擲出的例外。 這類未處理的例外狀況可能會導致您的應用程式失敗。 如果傳回 TaskTask<TResult> 的方法擲回例外狀況,則例外狀況會儲存在傳回的工作中。 當等待任務時,例外狀況會被重新拋出。 請確定任何可產生例外狀況的異步方法都有傳回類型為 TaskTask<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>

異步方法可以回傳任何類型,只要該類型具有可存取的 方法,而此方法會回傳一個awaiter 類型的實例。 此外,從 GetAwaiter 方法傳回的類型必須具有 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 屬性。 您可以在編譯程式工作類型產生器模式的 C# 規格閱讀 屬性一文中深入瞭解。

這個功能是對 可等待的表達式的互補,描述了 await操作數的需求。 一般化異步傳回型別可讓編譯程式產生傳回不同類型的 async 方法。 泛型化異步返回類型允許在 .NET 類庫中改進效能。 由於 TaskTask<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

撰寫泛用的異步傳回類型是進階應用場景,主要用於專用環境中。 請考慮改用 TaskTask<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 語句來列舉每個單詞。 方法會在需要從來源字串異步讀取下一行時等候。

另請參閱