C#异步介绍
创建多线程?太麻烦了!来试试更快更便捷的异步。程序无须按照代码顺序自上而下的执行。
什么是异步编程
什么是异步编程呢?举个简单的例子:
using System.Net.Http;
using System.Threading.Tasks;
using static System.Console;
namespace Core
{
class Async
{
static void Main()
{
Start();
End();
}
static void Wait()=>WriteLine("waiting...");
static void End()=>WriteLine("end...");
static int Start()
{
WriteLine("start...");
HttpClient client = new HttpClient();
Waiting();
var result = client.GetStringAsync("https://www.visualstudio.com/");
string str = result.Result;
return str.Length;
}
}
}
上面这段代码中,Main方法中的代码是按照自上而下的顺序执行的。网络状况不佳时,Start() 方法是比较耗时 (注意,这里在Start方法中调用了异步方法GetStringAsync,但该方法在此处是以同步方式执行的,具体原因下文会进行说明) ,在 Start() 方法执行完毕之前,整个程序处于阻塞状态。而异步编程可以很好的解决这个问题,一句简单的话来概括异步编程就是,程序无须按照代码顺序自上而下的执行。
async/await
C#5.0新增了async和await关键字,使用这两个关键字可以大大简化异步编程
使用 async 关键字可将方法、lambda 表达式或匿名方法标记为异步,即,方法中应该包含一个或多个await表达式,但async关键字本身不会创建异步操作。
public async Task Asy()
{
//do something...
}
这里需要注意一点,若使用async关键字标记的方法中没有使用await关键字(编译器会给出警告但不报错),那么该方法将会以同步方式执行。
定义异步方法的几点要求
定义一个异步方法应满足以下几点:
- 使用async关键字来修饰方法
- 在异步方法中使用await关键字(不使用编译器会给出警告但不报错),否则异步方法会以同步方式执行
- 尽量不使用void作为返回类型,若希望异步方法返回void类型,请使用Task
- 异步方法名称以Async结尾
- 异步方法中不能声明使用 ref 或 out 关键字修饰的变量
下面定义一个异步方法StartAsync():
static async Task<int> StartAsync()
{
HttpClient client = new HttpClient();
var str = await client.GetStringAsync("https://www.visualstudio.com/");
return str.Length;
}
异步返回类型
- Task(T) 返回类型
- 任务返回类型
- Void 返回类型
- 通用的异步返回类型和 ValueTask
异步方法可以具有以下返回类型:
Task
(对于返回值的异步方法)。 Task(对于执行操作但不返回任何值的异步方法)。
void(对于事件处理程序)。
从 C#7 开始,任何具有可访问的
GetAwaiter
方法的类型。GetAwaiter
方法返回的对象必须实现 System.Runtime.CompilerServices.ICriticalNotifyCompletion 接口。
有关异步方法的详细信息,请参阅使用 Async 和 Await 的异步编程 (C#)。
在以下其中一节检查每个返回类型,且在本主题末尾可以找到使用全部三种类型的完整示例。
Task(T) 返回类型
TaskTResult
。
在下面的示例中,GetLeisureHours
异步方法包含返回整数的 return
语句。 因此,该方法声明必须指定 Task<int>
的返回类型。 FromResult 异步方法是返回字符串的操作的占位符。
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
的调用,你可以更好地了解此操作,如下面的代码所示。 对非立即等待的方法 TaskOfT_MethodAsync
的调用返回 Task<int>
,正如你从方法声明预料的一样。 该任务指派给示例中的 integerTask
变量。 因为 integerTask
是 TaskTResult
的 Result 属性。 在这种情况下,TResult
表示整数类型。 await
应用于 integerTask
,await 表达式的计算结果为 integerTask
的 Result 属性内容。 此值分配给 result2
变量。
重要:Result 属性为阻止属性。 如果你在其任务完成之前尝试访问它,当前处于活动状态的线程将被阻止,直到任务完成且值为可用。 在大多数情况下,应通过使用 await 访问此值,而不是直接访问属性。
上一示例通过检索 Result 属性的值来阻止主线程,从而使 ShowTodaysInfo 方法可在应用程序结束之前完成执行。
var infoTask = 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 infoTask}";
任务返回类型
不包含 return
语句的异步方法或包含不返回操作数的 return
语句的异步方法通常具有返回类型 Task。 如果此类方法同步运行,它们将返回 void
。 如果在异步方法中使用 Task 返回类型,调用方法可以使用 await
运算符暂停调用方的完成,直至被调用的异步方法结束。
如下示例中,WaitAndApologize
异步方法不包含 return 语句,因此此方法返回 Task 对象。 通过这样可等待 WaitAndApologize
。 请注意,Task 类型不包含 Result
属性,因为它不具有任何返回值。
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 运算符的应用程序在这种情况下不生成值。
如同上一个 TaskTask_MethodAsync
的调用,如以下代码所示。 但是,请记住,Task
没有 Result
属性,并且当 await 运算符应用于 Task
时不产生值。
以下代码将调用 WaitAndApologize
方法和等待此方法返回的任务分离。
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
有关如何在异步方法中捕捉异常的更多信息,请参阅 try-catch。
以下示例定义一个异步事件处理程序。
using System;
using System.Threading.Tasks;
public class Counter
{
private int threshold = 0;
private int iterations = 0;
private int ctr = 0;
event EventHandler<EventArgs> ThresholdReached;
public Counter(int threshold)
{
this.threshold = threshold;
ThresholdReached += thresholdReachedEvent;
}
public async Task<int> StartCounting(int limit)
{
iterations = 1;
for (int index = 0; index <= limit; index++) {
if (ctr == threshold)
thresholdReachedEvent(this, EventArgs.Empty);
ctr++;
await Task.Delay(500);
}
int retval = ctr + (iterations - 1) * threshold;
Console.WriteLine($"On iteration {iterations}, reached {limit}");
return retval;
}
async void thresholdReachedEvent(object sender, EventArgs e)
{
Console.WriteLine($"Reached {ctr}. Resetting...");
await Task.Delay(1000);
ctr = 0;
iterations++;
}
}
public class Example
{
public static void Main()
{
RunCounter().Wait();
}
private static async Task RunCounter()
{
var count = new Counter(5);
await count.StartCounting(8);
}
}
通用的异步返回类型和 ValueTask
从 C# 7 开始,异步方法可返回任何具有可访问的 GetAwaiter
方法的类型。
Task 和 Task
.NET 提供 System.Threading.Tasks.ValueTaskSystem.Threading.Tasks.Extensions
NuGet 包。 如下示例使用 ValueTask
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