非同步傳回類型 (c # )

非同步方法可有下列傳回型別:

如需非同步方法的詳細資訊,請參閱 使用 async 和 await 進行非同步程式設計 (c # )

另外還有幾個其他類型是 Windows 工作負載特有的:

工作傳回類型

不包含 return 陳述式的非同步方法,或包含不會傳回運算元的 return 陳述式的非同步方法,通常具有傳回型別 Task。 這類方法如果以同步方式執行,會傳回 void。 如果您針對非同步方法使用 Task 傳回型別,則除非被呼叫的非同步方法完成,否則呼叫的方法可以使用 await 運算子暫止呼叫端完成。

在下列範例中, WaitAndApologizeAsync 方法不包含 return 語句,因此方法會傳回 Task 物件。 傳回可 Task 等候 WaitAndApologizeAsyncTask型別不包含 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 Taskawait 和其運算元為語句。

您可以將的呼叫與 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> 型別用於非同步方法,其中包含運算元為的 return 語句 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

ShowTodaysInfo 方法的 await 運算式內呼叫 GetLeisureHoursAsync 時,await 運算式會擷取儲存在 GetLeisureHours 方法所傳回之工作中的整數值 (leisureHours 的值)。 如需 await 運算式的詳細資訊,請參閱 await

await Task<T> GetLeisureHoursAsync 如下列程式碼所示,您可以進一步瞭解如何從的應用程式分隔呼叫,以抓取的結果 await 。 呼叫不會立即等候的 GetLeisureHoursAsync 方法,會傳回 Task<int>,如您所預期的方法宣告。 在範例中,工作會指派給 getLeisureHoursTask 變數。 因為 getLeisureHoursTaskTask<TResult>,所以它包含 TResult 類型的 Result 屬性。 在本例中,TResult 代表整數類型。 當 await 套用至 getLeisureHoursTask 時,await 運算式評估為 getLeisureHoursTaskResult 屬性的內容。 值會指派給 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> ,而且會等候該方法的呼叫。

如需如何在非同步方法中攔截例外狀況的詳細資訊,請參閱try-catch文章的非同步方法中的例外狀況一節。

下列範例示範非同步事件處理常式的行為。 在範例程式碼中,非同步事件處理常式必須讓主執行緒知道它完成的時間。 然後主執行緒可以等候非同步事件處理常式完成,再結束程式。

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.

通用的非同步傳回型別和 ValueTask<TResult>

從 c # 7.0 開始,非同步方法可以傳回具有可存取方法的任何型別, GetAwaiter 此方法會傳回 awaiter 類型 的實例。 此外,從方法傳回的型別 GetAwaiter 必須有 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 屬性。 您可以在本文中深入瞭解 編譯器所讀取的屬性 或工作 例如傳回型別)的功能規格。

這項功能是 可等候運算式的補充,可描述運算元的需求 await 。 一般化的非同步傳回型別可讓編譯器產生傳回 async 不同類型的方法。 一般化的非同步傳回型別在 .NET 程式庫中啟用了效能改進。 因為 TaskTask<TResult> 是參考型別,所以效能關鍵路徑中的記憶體配置,會對效能造成不良影響,特別是當配置出現在緊密迴圈中時。 支援通用的傳回型別,表示您可以傳回輕量型的實值型別,而不是參考型別,以避免額外的記憶體配置。

.NET 提供 System.Threading.Tasks.ValueTask<TResult> 結構作為通用工作傳回值的輕量級實作。 若要使用 System.Threading.Tasks.ValueTask<TResult> 類型,您必須將 System.Threading.Tasks.Extensions NuGet 套件新增至專案。 下列範例會使用 ValueTask<TResult> 結構,擷取擲兩次骰子的值。

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

撰寫一般化的非同步傳回型別是一種 advanced 案例,其目標是要在專用環境中使用。 請考慮 Task Task<T> 改為使用、和 ValueTask<T> 類型,其中涵蓋非同步程式碼的大部分案例。

在 c # 10.0 和更新版本中,您可以將 AsyncMethodBuilder 屬性套用至非同步方法 (而不是非同步傳回型別宣告) 覆寫該類型的產生器。 通常您會套用此屬性,以使用 .NET 執行時間中提供的不同產生器。

使用 IAsyncEnumerable 的非同步資料流程<T>

從 c # 8.0 開始,非同步方法可能會傳回以表示的 非同步資料流程 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 。 當方法需要以非同步方式從來源字串讀取下一行時,方法就會等候。

另請參閱