异步返回类型 (C#)
异步方法可以具有以下返回类型:
- Task(对于执行操作但不返回任何值的异步方法)。
- Task<TResult>(对于返回值的异步方法)。
void
(对于事件处理程序)。- 任何具有可访问的
GetAwaiter
方法的类型。GetAwaiter
方法返回的对象必须实现 System.Runtime.CompilerServices.ICriticalNotifyCompletion 接口。 - IAsyncEnumerable<T>(对于返回异步流的异步方法)。
有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)。
还存在特定于 Windows 工作负载的其他几种类型:
- DispatcherOperation,适用于仅限于 Windows 的异步操作。
- IAsyncAction,适用于 UWP 中不返回值的异步操作。
- IAsyncActionWithProgress<TProgress>,适用于 UWP 中只报告进程但不返回值的异步操作。
- IAsyncOperation<TResult>,适用于 UWP 中返回值的异步操作。
- IAsyncOperationWithProgress<TResult,TProgress>,适用于 UWP 中既报告进程又返回值的异步操作。
Task 返回类型
不包含 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.
通过使用 await 语句而不是 await 表达式等待 WaitAndApologizeAsync
,类似于返回 void 的同步方法的调用语句。 Await 运算符的应用程序在这种情况下不生成值。 当 await
的右操作数是 Task<TResult> 时,await
表达式生成的结果为 T
。 当 await
的右操作数是 Task 时,await
及其操作数是一个语句。
可从 await 运算符的应用程序中分离对 WaitAndApologizeAsync
的调用,如以下代码所示。 但是,请记住,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);
Task<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
中分离对 GetLeisureHoursAsync
的调用,可以更好地理解 await
如何从 Task<T>
检索结果,如以下代码所示。 对非立即等待的方法 GetLeisureHoursAsync
的调用返回 Task<int>
,正如你从方法声明预料的一样。 该任务指派给示例中的 getLeisureHoursTask
变量。 因为 getLeisureHoursTask
是 Task<TResult>,所以它包含类型 TResult
的 Result 属性。 在这种情况下,TResult
表示整数类型。 await
应用于 getLeisureHoursTask
,await 表达式的计算结果为 getLeisureHoursTask
的 Result 属性内容。 此值分配给 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>
异步方法可以返回具有返回 awaiter 类型实例的可访问 GetAwaiter
方法的所有类型。 此外,GetAwaiter
方法返回的类型必须具有 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 特性。 可以通过有关编译器读取的属性的文章或任务类型生成器模式的 C# 规范,了解详细信息。
此功能与 awaitable 表达式相辅相成,后者描述 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>
类型,它们适用于大多数的异步代码方案。
在 C# 10 及更高版本中,可以将 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
语句枚举每个单词。 当需要从源字符串异步读取下一行时,该方法将等待。
请参阅
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈