异步方法可以具有以下返回类型:
Task
Task(对于执行操作但不返回任何值的异步方法)。
void
(对于事件处理程序)。
从 C# 7.0 开始,任何具有可访问的 GetAwaiter
方法的类型。 GetAwaiter
方法返回的对象必须实现 System.Runtime.CompilerServices.ICriticalNotifyCompletion 接口。
有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)。
在以下其中一节检查每个返回类型,且在本主题末尾可以找到使用全部三种类型的完整示例。
TaskTResult
。
在下面的示例中,GetLeisureHours
异步方法包含返回整数的 return
语句。 因此,该方法声明必须指定 Task
的返回类型。 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 ShowTodaysInfo()
{
string ret = $"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHours()}";
return ret;
}
static async Task GetLeisureHours()
{
// Task.FromResult is a placeholder for actual work that returns a string.
var today = await Task.FromResult(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
的值)。 有关 await 表达式的详细信息,请参阅 await。
通过从应用程序 await
中分离对 GetLeisureHours
的调用,你可以更好地了解此操作,如下面的代码所示。 对非立即等待的方法 GetLeisureHours
的调用返回 Task
,正如你从方法声明预料的一样。 该任务指派给示例中的 integerTask
变量。 因为 integerTask
是 TaskTResult
的 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 运算符的应用程序在这种情况下不生成值。
如同上一个 TaskWaitAndApologize
的调用,如以下代码所示。 但是,请记住,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
返回类型。 对于事件处理程序以外的不返回值的方法,应返回 Task,因为无法等待返回 void
的异步方法。 这种方法的任何调用方必须能够继续完成,而无需等待调用的异步方法完成,并且调用方必须独立于异步方法生成的任何值或异常。
返回 void 的异步方法的调用方无法捕获从该方法引发的异常,且此类未经处理的异常可能会导致应用程序故障。 如果返回 Task 或 Task
有关如何在异步方法中捕获异常的详细信息,请参阅 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 tcs;
static async Task Main()
{
tcs = new TaskCompletionSource();
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.
从 C# 7.0 开始,异步方法可返回任何具有可访问的 GetAwaiter
方法的类型。
Task 和 Task
.NET 提供 System.Threading.Tasks.ValueTaskSystem.Threading.Tasks.Extensions
NuGet 包。 如下示例使用 ValueTask
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 GetDiceRoll()
{
Console.WriteLine("...Shaking the dice...");
int roll1 = await Roll();
int roll2 = await Roll();
return roll1 + roll2;
}
private static async ValueTask 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