异步返回类型 (C#)
-
异步方法可以具有以下返回类型:
-
Task<TResult>(对于返回值的异步方法)。
-
Task(对于执行操作但不返回任何值的异步方法)。
-
void
(对于事件处理程序)。 -
从 C# 7.0 开始,任何具有可访问的
GetAwaiter
方法的类型。GetAwaiter
方法返回的对象必须实现 System.Runtime.CompilerServices.ICriticalNotifyCompletion 接口。
有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)。
在以下其中一节检查每个返回类型,且在本主题末尾可以找到使用全部三种类型的完整示例。
Task<TResult> 返回类型
Task<TResult> 返回类型用于某种异步方法,此异步方法包含 return (C#) 语句,其中操作数具有类型
TResult
。在下面的示例中,
GetLeisureHours
异步方法包含返回整数的return
语句。 因此,该方法声明必须指定Task<int>
的返回类型。 FromResult 异步方法是返回字符串的操作的占位符。C#复制
using System; using System.Linq; using System.Threading.Tasks; public class Example { public static void Main() { Console.WriteLine(ShowTodaysInfo().Result); } private 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 // </Snippet >
在
ShowTodaysInfo
方法中从 await 表达式内调用GetLeisureHours
时,await 表达式检索存储在由GetLeisureHours
方法返回的任务中的整数值(leisureHours
的值)。 有关 await 表达式的详细信息,请参阅 await。通过从应用程序
await
中分离对GetLeisureHours
的调用,你可以更好地了解此操作,如下面的代码所示。 对非立即等待的方法GetLeisureHours
的调用返回Task<int>
,正如你从方法声明预料的一样。 该任务指派给示例中的integerTask
变量。 因为integerTask
是 Task<TResult>,所以它包含类型TResult
的 Result 属性。 在这种情况下,TResult
表示整数类型。await
应用于integerTask
,await 表达式的计算结果为integerTask
的 Result 属性内容。 此值分配给ret
变量。重要
Result 属性为阻止属性。 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。 在大多数情况下,应通过使用
await
访问此值,而不是直接访问属性。
上一示例通过检索 Result 属性的值来阻止主线程,从而使ShowTodaysInfo
方法可在应用程序结束之前完成执行。C#复制
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
语句的异步方法或包含不返回操作数的return
语句的异步方法通常具有返回类型 Task。 如果此类方法同步运行,它们将返回void
。 如果在异步方法中使用 Task 返回类型,调用方法可以使用await
运算符暂停调用方的完成,直至被调用的异步方法结束。如下示例中,
WaitAndApologize
异步方法不包含return
语句,因此此方法返回 Task 对象。 通过这样可等待WaitAndApologize
。 请注意,Task 类型不包含Result
属性,因为它不具有任何返回值。C#复制
using System; using System.Threading.Tasks; public class Example { public static void Main() { DisplayCurrentInfo().Wait(); } 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 的同步方法的调用语句。 Await 运算符的应用程序在这种情况下不生成值。如同上一个 Task<TResult> 示例,可以从 await 运算符的应用程序中分离对
WaitAndApologize
的调用,如以下代码所示。 但是,请记住,Task
没有Result
属性,并且当 await 运算符应用于Task
时不产生值。以下代码将调用
WaitAndApologize
方法和等待此方法返回的任务分离。C#复制
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
返回类型,这需要void
返回类型。 对于事件处理程序以外的不返回值的方法,应返回 Task,因为无法等待返回void
的异步方法。 这种方法的任何调用方必须能够继续完成,而无需等待调用的异步方法完成,并且调用方必须独立于异步方法生成的任何值或异常。返回 void 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。 如果返回 Task 或 Task<TResult> 的异步方法中出现异常,此异常将存储于返回的任务中,并在等待该任务时再次引发。 因此,请确保可以产生异常的任何异步方法都具有返回类型 Task或 Task<TResult>,并确保会等待对方法的调用。
有关如何在异步方法中捕获异常的详细信息,请参阅 try-catch 主题的异步方法中的异常部分。
以下示例演示异步事件处理程序的行为。 请注意,在本示例代码中,异步事件处理程序必须在完成时通知主线程。 然后,主线程可在退出程序之前等待异步事件处理程序完成。
C#复制
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; 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>
从 C# 7.0 开始,异步方法可返回任何具有可访问的
GetAwaiter
方法的类型。Task 和 Task<TResult> 是引用类型,因此,性能关键路径中的内存分配会对性能产生负面影响,尤其当分配出现在紧凑循环中时。 支持通用返回类型意味着可返回轻量值类型(而不是引用类型),从而避免额外的内存分配。
.NET 提供 System.Threading.Tasks.ValueTask<TResult> 结构作为返回任务的通用值的轻量实现。 要使用 System.Threading.Tasks.ValueTask<TResult> 类型,必须向项目添加
System.Threading.Tasks.Extensions
NuGet 包。 如下示例使用 ValueTask<TResult> 结构检索两个骰子的值。C#复制
using System; using System.Threading.Tasks; class Program { static Random rnd; static void Main() { Console.WriteLine($"You rolled {GetDiceRoll().Result}"); } private static async ValueTask<int> GetDiceRoll() { Console.WriteLine("...Shaking the dice..."); int roll1 = await Roll(); int roll2 = await Roll(); return roll1 + roll2; } private static async ValueTask<int> Roll() { 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
请参阅
-