使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法。
public async Task<int> ExampleMethodAsync()
{
// . . . .
}
如果你不熟悉异步编程或不了解异步方法如何使用 await 关键字来完成可能需要长时间运行的工作而不阻止调用方的线程,请阅读使用 async 和 await 的异步编程中的简介。
string contents = await contentsTask;
方法将同步运行,直至到达其第一个 await 表达式,此时会将方法挂起,直到等待的任务完成。 同时,如下节示例中所示,控件将返回到方法的调用方。
如果 async 关键字修改的方法不包含 await 表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含 await 的任何异步方法,因为该情况可能表示存在错误。 请参阅编译器警告(等级 1)CS4014。
async 关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。 在所有其他上下文中,都会将其解释为标识符。
示例
下面的示例展示了异步事件处理程序 StartButton_Click 和异步方法 ExampleMethodAsync 之间的控制结构和流程。 此异步方法得到的结果是一个下载网站的长度。 此代码适用于在 Visual Studio 中创建的 Windows Presentation Foundation (WPF) 应用或 Windows 应用商店应用;请参见有关设置应用的代码注释。
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
ResultsTextBox.Text += "\n";
try
{
int length = await ExampleMethodAsync();
ResultsTextBox.Text += String.Format("Length: {0}\n", length);
}
catch (Exception)
{
}
}
public async Task<int> ExampleMethodAsync()
{
var httpClient = new HttpClient();
int exampleInt = (await httpClient.GetStringAsync("http://msdn.microsoft.com")).Length;
ResultsTextBox.Text += "Preparing to finish ExampleMethodAsync.\n";
return exampleInt;
}
// Output:
// Preparing to finish ExampleMethodAsync.
// Length: 53292
返回类型
异步方法的返回类型可为 Task
、Task<TResult>
或 void
。 方法不能声明任何 ref 或 out 参数,但是可以调用具有这类参数的方法。
如果异步方法的 语句指定一个 类型的操作数,则应指定 Task<TResult>
作为方法的返回类型TResult。 如果当方法完成时未返回有意义的值,则应使用 Task
。 即,对方法的调用将返回一个 Task,但是当 Task
完成时,任何等待 await 的所有 Task
表达式的计算结果都为 void。
你应主要使用 void 返回类型来定义事件处理程序,这些处理程序需要此返回类型。 void 返回异步方法的调用方不能等待,并且无法捕获该方法引发的异常。
await 运算符在异步方法应用于任务,以挂起执行方法,直到所等待的任务完成。 任务表示正在进行的工作。
在其中使用 await 的异步方法必须通过 async 关键字进行修改。 使用 async 修饰符定义并且通常包含一个或多个 await 表达式的这类方法称为异步方法。
应用 await 运算符的任务通常是实现基于任务的异步模式的方法调用的返回值。 示例包括类型 Task
或 Task<TResult>
的值。
在下列代码中,HttpClient
方法GetByteArrayAsync
返回 Task\<byte[]>
和 getContentsTask
。 当任务完成时,任务约定生成实际字节数组。 await
运算符应用于 getContentsTask
以在 SumPageSizesAsync
中挂起执行,直到 getContentsTask
完成。 同时,控制权会返回给 SumPageSizesAsync
的调用方。 当 getContentsTask
完成之后,await
表达式计算为字节数组。
private async Task SumPageSizesAsync()
{
//要在桌面应用程序中使用HttpClient类型,您必须包含using指令,并为System.Net.Http命名空间添加引用。
HttpClient client = new HttpClient();
// . . .
Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
byte[] urlContents = await getContentsTask;
//byte[] urlContents = await client.GetByteArrayAsync(url);
// . . .
}
如前面的示例中所示,如果 await
应用于返回 Task<TResult>
的方法调用结果,则 await
表达式的类型为 TResult
。 如果 await
应用于返回 Task
的方法调用结果,则 await
表达式的类型为 void。 以下示例演示了差异。
// 关键字await用于 返回任务<TResult>的方法
TResult result = await AsyncMethodThatReturnsTaskTResult();
//关键字await与返回任务的方法一起使用
await AsyncMethodThatReturnsTask();
await
表达式不阻止正在执行它的线程。 而是使编译器将剩下的异步方法注册为等待任务的延续任务。 控制权随后会返回给异步方法的调用方。 任务完成时,它会调用其延续任务,异步方法的执行会在暂停的位置处恢复。
await
表达式只能在由 async
修饰符标记的立即封闭方法体、lambda
表达式或异步方法中出现。 术语 await
在该上下文中仅用作关键字。 在其他位置,它会解释为标识符。 在方法、Lambda 表达式或匿名方法中,await
表达式不能在同步函数体、查询表达式、lock
语句块或不安全上下文中出现。
异常
大多数异步方法将返回 Task
或 Task<TResult>
。 返回任务的属性携带有关其状态和历史记录的信息,如任务是否完成、异步方法是否导致异常或已取消以及最终结果是什么。 await
运算符可访问这些属性。
如果等待的任务返回异步方法导致异常,则 await
运算符会重新引发异常。
如果等待的任务返回异步方法取消,则 await
运算符会重新引发 OperationCanceledException
。
处于故障状态的单个任务可以反映多个异常。 例如,任务可能是调用 Task.WhenAll
的结果。 等待此类任务时,等待操作仅重新引发异常之一。 但是,无法预测重新引发的异常。
有关异步方法中的错误处理的示例,请参阅 try catch。
示例
下面的 Windows 窗体示例阐释如何在异步方法 WaitAsynchronouslyAsync
中使用 await
。 将该方法的行为与 WaitSynchronously
的行为进行对比。 如果未向任务应用 await 运算符,WaitSynchronously
就会同步运行,而不管其定义中是否使用了 async
修饰符和在主体中是否调用了 Thread.Sleep
。
private async void button1_Click(object sender, EventArgs e)
{
//调用异步运行的方法
string result = await WaitAsynchronouslyAsync();
//调用同步运行的方法
//string result = await WaitSynchronously ();
//显示结果
textBox1.Text += result;
}
//以下方法异步运行。 延迟期间UI线程不被阻止。 您可以在Task.Delay运行时移动或调整Form1窗口大小。
public async Task<string> WaitAsynchronouslyAsync()
{
await Task.Delay(10000);
return "Finished";
}
//尽管使用异步,但以下方法同步运行。
//由于UI线程被阻止,因此Thread.Sleep正在运行时,无法移动或调整Form1窗口的大小。
public async Task<string> WaitSynchronously()
{
//为System.Threading添加一个using指令
Thread.Sleep(10000);
return "Finished";
}