异步返回类型 (C#)Async Return Types (C#)

异步方法可以具有以下返回类型:Async methods can have the following return types:

有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)For more information about async methods, see Asynchronous Programming with async and await (C#).

Task<TResult> 返回类型Task<TResult> Return Type

Task<TResult> 返回类型用于某种异步方法,此异步方法包含 return (C#) 语句,其中操作数是 TResultThe Task<TResult> return type is used for an async method that contains a return (C#) statement in which the operand is TResult.

在下面的示例中,GetLeisureHours 异步方法包含返回整数的 return 语句。In the following example, the GetLeisureHours async method contains a return statement that returns an integer. 因此,该方法声明必须指定 Task<int> 的返回类型。Therefore, the method declaration must specify a return type of Task<int>. FromResult 异步方法是返回字符串的操作的占位符。The FromResult async method is a placeholder for an operation that returns a string.

public static async Task<string> ShowTodaysInfo()
{
    string ret = $"Today is {DateTime.Today:D}\n" +
                 "Today's hours of leisure: " +
                 $"{await GetLeisureHours()}";
    return ret;
}

static async Task<int> GetLeisureHours()
{
    // Task.FromResult is a placeholder for actual work that returns a string.
    var today = await Task.FromResult<string>(DateTime.Now.DayOfWeek.ToString());

    // The method then can process the result in some way.
    int leisureHours;
    if (today.First() == 'S')
        leisureHours = 16;
    else
        leisureHours = 5;

    return leisureHours;
}
// The example displays output like the following:
//    Today is Wednesday, May 24, 2017
//    Today's hours of leisure: 5

ShowTodaysInfo 方法中从 await 表达式内调用 GetLeisureHours 时,await 表达式检索存储在由 GetLeisureHours 方法返回的任务中的整数值(leisureHours 的值)。When GetLeisureHours is called from within an await expression in the ShowTodaysInfo method, the await expression retrieves the integer value (the value of leisureHours) that's stored in the task returned by the GetLeisureHours method. 有关 await 表达式的详细信息,请参阅 awaitFor more information about await expressions, see await.

通过从应用程序 await 中分离对 GetLeisureHours 的调用,可以更好地理解 await 如何从 Task<T> 检索结果,如以下代码所示。You can better understand how await retrieves the result from a Task<T> by separating the call to GetLeisureHours from the application of await, as the following code shows. 对非立即等待的方法 GetLeisureHours 的调用返回 Task<int>,正如你从方法声明预料的一样。A call to method GetLeisureHours that isn't immediately awaited returns a Task<int>, as you would expect from the declaration of the method. 该任务指派给示例中的 integerTask 变量。The task is assigned to the integerTask variable in the example. 因为 integerTaskTask<TResult>,所以它包含类型 TResultResult 属性。Because integerTask is a Task<TResult>, it contains a Result property of type TResult. 在这种情况下,TResult 表示整数类型。In this case, TResult represents an integer type. await 应用于 integerTask,await 表达式的计算结果为 integerTaskResult 属性内容。When await is applied to integerTask, the await expression evaluates to the contents of the Result property of integerTask. 此值分配给 ret 变量。The value is assigned to the ret variable.

重要

Result 属性为阻止属性。The Result property is a blocking property. 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。If you try to access it before its task is finished, the thread that's currently active is blocked until the task completes and the value is available. 在大多数情况下,应通过使用 await 访问此值,而不是直接访问属性。In most cases, you should access the value by using await instead of accessing the property directly.
上一示例通过检索 Result 属性的值来阻止主线程,从而使 ShowTodaysInfo 方法可在应用程序结束之前完成执行。The previous example retrieved the value of the Result property to block the main thread so that the ShowTodaysInfo method could finish execution before the application ended.

var integerTask = GetLeisureHours();

// You can do other work that does not rely on integerTask before awaiting.
string ret = $"Today is {DateTime.Today:D}\n" +
             "Today's hours of leisure: " +
             $"{await integerTask}";
return ret;

Task 返回类型Task Return Type

不包含 return 语句的异步方法或包含不返回操作数的 return 语句的异步方法通常具有返回类型 TaskAsync methods that don't contain a return statement or that contain a return statement that doesn't return an operand usually have a return type of Task. 如果此类方法同步运行,它们将返回 voidSuch methods return void if they run synchronously. 如果在异步方法中使用 Task 返回类型,调用方法可以使用 await 运算符暂停调用方的完成,直至被调用的异步方法结束。If you use a Task return type for an async method, a calling method can use an await operator to suspend the caller's completion until the called async method has finished.

如下示例中,WaitAndApologize 异步方法不包含 return 语句,因此此方法返回 Task 对象。In the following example, the WaitAndApologize async method doesn't contain a return statement, so the method returns a Task object. 返回 Task 可等待 WaitAndApologizeReturning a Task enables WaitAndApologize to be awaited. Task 类型不包含 Result 属性,因为它不具有任何返回值。The Task type doesn't include a Result property because it has no return value.

public static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    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 WaitAndApologize()
{
    // Task.Delay is a placeholder for actual work.
    await Task.Delay(2000);
    // Task.Delay delays the following line by two seconds.
    Console.WriteLine("\nSorry for the delay. . . .\n");
}
// The example displays the following output:
//    Sorry for the delay. . . .
//
//    Today is Wednesday, May 24, 2017
//    The current time is 15:25:16.2935649
//    The current temperature is 76 degrees.

通过使用 await 语句而不是 await 表达式等待 WaitAndApologize,类似于返回 void 的同步方法的调用语句。WaitAndApologize is awaited by using an await statement instead of an await expression, similar to the calling statement for a synchronous void-returning method. Await 运算符的应用程序在这种情况下不生成值。The application of an await operator in this case doesn't produce a value.

如同上一个 Task<TResult> 示例,可以从 await 运算符的应用程序中分离对 WaitAndApologize 的调用,如以下代码所示。As in the previous Task<TResult> example, you can separate the call to WaitAndApologize from the application of an await operator, as the following code shows. 但是,请记住,Task 没有 Result 属性,并且当 await 运算符应用于 Task 时不产生值。However, remember that a Task doesn't have a Result property, and that no value is produced when an await operator is applied to a Task.

以下代码将调用 WaitAndApologize 方法和等待此方法返回的任务分离。The following code separates calling the WaitAndApologize method from awaiting the task that the method returns.

Task wait = WaitAndApologize();

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 wait;
Console.WriteLine(output);

Void 返回类型Void return type

在异步事件处理程序中使用 void 返回类型,这需要 void 返回类型。You use the void return type in asynchronous event handlers, which require a void return type. 对于事件处理程序以外的不返回值的方法,应返回 Task,因为无法等待返回 void 的异步方法。For methods other than event handlers that don't return a value, you should return a Task instead, because an async method that returns void can't be awaited. 此类方法的任何调用方都必须继续完成,而无需等待调用的异步方法完成。Any caller of such a method must continue to completion without waiting for the called async method to finish. 调用方必须独立于异步方法生成的任何值或异常。The caller must be independent of any values or exceptions that the async method generates.

返回 void 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。The caller of a void-returning async method can't catch exceptions thrown from the method, and such unhandled exceptions are likely to cause your application to fail. 如果返回 TaskTask<TResult> 的方法引发异常,则该异常存储在返回的任务中。If a method that returns a Task or Task<TResult> throws an exception, the exception is stored in the returned task. 等待任务时,将重新引发异常。The exception is rethrown when the task is awaited. 因此,请确保可以产生异常的任何异步方法都具有返回类型 TaskTask<TResult>,并确保会等待对方法的调用。Therefore, make sure that any async method that can produce an exception has a return type of Task or Task<TResult> and that calls to the method are awaited.

有关如何在异步方法中捕获异常的详细信息,请参阅 try-catch 文中的异步方法中的异常部分。For more information about how to catch exceptions in async methods, see the Exceptions in Async Methods section of the try-catch article.

以下示例演示异步事件处理程序的行为。The following example shows the behavior of an async event handler. 在本示例代码中,异步事件处理程序必须在完成时通知主线程。In the example code, an async event handler must let the main thread know when it finishes. 然后,主线程可在退出程序之前等待异步事件处理程序完成。Then the main thread can wait for an async event handler to complete before exiting the program.

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 TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();

    public static async Task Main()
    {
        tcs = new TaskCompletionSource<bool>();
        var secondHandlerFinished = tcs.Task;

        var button = new NaiveButton();
        button.Clicked += Button_Clicked_1;
        button.Clicked += Button_Clicked_2_Async;
        button.Clicked += Button_Clicked_3;

        Console.WriteLine("About to click a button...");
        button.Click();
        Console.WriteLine("Button's Click method returned.");

        await secondHandlerFinished;
    }

    private static void Button_Clicked_1(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 1 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 1 is done.");
    }

    private static async void Button_Clicked_2_Async(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.");
        tcs.SetResult(true);
    }

    private static void Button_Clicked_3(object? sender, EventArgs e)
    {
        Console.WriteLine("   Handler 3 is starting...");
        Task.Delay(100).Wait();
        Console.WriteLine("   Handler 3 is done.");
    }
}

// Expected output:
// About to click a button...
// 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.
// Button's Click method returned.
//    Handler 2 is done.

通用的异步返回类型和 ValueTask<TResult>Generalized async return types and ValueTask<TResult>

从 C# 7.0 开始,异步方法可返回任何具有可访问的 GetAwaiter 方法的类型。Starting with C# 7.0, an async method can return any type that has an accessible GetAwaiter method.

TaskTask<TResult> 是引用类型,因此,性能关键路径中的内存分配会对性能产生负面影响,尤其当分配出现在紧凑循环中时。Because Task and Task<TResult> are reference types, memory allocation in performance-critical paths, particularly when allocations occur in tight loops, can adversely affect performance. 支持通用返回类型意味着可返回轻量值类型(而不是引用类型),从而避免额外的内存分配。Support for generalized return types means that you can return a lightweight value type instead of a reference type to avoid additional memory allocations.

.NET 提供 System.Threading.Tasks.ValueTask<TResult> 结构作为返回任务的通用值的轻量实现。.NET provides the System.Threading.Tasks.ValueTask<TResult> structure as a lightweight implementation of a generalized task-returning value. 要使用 System.Threading.Tasks.ValueTask<TResult> 类型,必须向项目添加 System.Threading.Tasks.Extensions NuGet 包。To use the System.Threading.Tasks.ValueTask<TResult> type, you must add the System.Threading.Tasks.Extensions NuGet package to your project. 如下示例使用 ValueTask<TResult> 结构检索两个骰子的值。The following example uses the ValueTask<TResult> structure to retrieve the value of two dice rolls.

using System;
using System.Threading.Tasks;

class Program
{
   static Random? rnd;

   static async Task Main()
   {
      Console.WriteLine($"You rolled {await GetDiceRollAsync()}");
   }

   private static async ValueTask<int> GetDiceRollAsync()
   {
      Console.WriteLine("...Shaking the dice...");
      int roll1 = await RollAsync();
      int roll2 = await RollAsync();
      return roll1 + roll2;
   }

   private static async ValueTask<int> RollAsync()
   {
      if (rnd == null)
         rnd = new Random();

      await Task.Delay(500);
      int diceRoll = rnd.Next(1, 7);
      return diceRoll;
   }
}
// The example displays output like the following:
//       ...Shaking the dice...
//       You rolled 8

使用 IAsyncEnumerable<T> 的异步流Async streams with IAsyncEnumerable<T>

从 C# 8.0 开始,异步方法可能返回异步流,由 IAsyncEnumerable<T> 表示 。Starting with C# 8.0, an async method may return an async stream, represented by IAsyncEnumerable<T>. 异步流提供了一种方法,来枚举在具有重复异步调用的块中生成元素时从流中读取的项。An async stream provides a way to enumerate items read from a stream when elements are generated in chunks with repeated asynchronous calls. 以下示例显示生成异步流的异步方法:The following example shows an async method that generates an async stream:

private static async IAsyncEnumerable<string> ReadWordsFromStream()
{
    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)
    {
        var words = line.Split(' ',StringSplitOptions.RemoveEmptyEntries);
        foreach (var word in words)
        {
            yield return word;
        }
        line = await readStream.ReadLineAsync();
    }
}

前面的示例异步读取字符串中的行。The preceding example reads lines from a string asynchronously. 读取每一行后,代码将枚举字符串中的每个单词。Once each line is read, the code enumerates each word in the string. 调用方将使用 await foreach 语句枚举每个单词。Callers would enumerate each word using the await foreach statement. 当需要从源字符串异步读取下一行时,该方法将等待。The method awaits when it needs to asynchronously read the next line from the source string.

请参阅See also