转载地址:https://zhuanlan.zhihu.com/p/463340854
Async/Await 已经出来很多年,虽然本人是个老码农,但对C#仍然是初学者,所以在学习的过程中做个记录,算备忘把。
为何需要Async/Await?
为何需要Async/Await?因为异步编程需要啊!
异步编程不是早有了吗,为何需要Async/Await?没有Async/Await,你当然可以用传统手段实现异步编程,创建一个thread,在把干活的函数传递到thread,然后外面继续干别的活,外面干的差不多了,就去等thread的结果,然后处理结果,搽屁股…,这一通下来,几十行代码没跑了,不仅如此,你自己写的传统方式的代码还可能出现各种问题(比如屁股没擦干净)。(题外话:写了多年的C,表示很伤心)
有了Async/Await,你只需要几行代码就可搞定,而且更安全,更放心。不香吗?不香吗!
有些同学对异步编程可能没啥概念,这里举个例子:你在写一个GUI的应用程序,这个程序需要加载一个特别大的文件,这个过程可能需要30s:
如果是同步编程模式,在加载文件的这30s里面,用户是不能进行任何操作的,比如点个按钮什么的,换做你,你会不会认为程序是不是挂了(鬼知道文件加载的怎么样了?当然smart一点你可以搞一个bar来显示,但也需要异步哦)
如果是异步编程模式,你可以选在“在后台”加载这个文件,主线程仍然可以serve用户的操作,比如点个按钮啥的,如果用户要操作文件,你可以很nice的告诉用户文件正在加载中,加载多少了等等,明显这个用户体验更佳。
Async/Await的关键点总结
先上干活,把要点总结一下,看不懂没关系,直接跳过即可。之所以把要点放前面是因为代码太长,不想把重点挤到太后面了。
Async是C#的修饰符,可以用来修饰method或者expression,用来声明一个异步的method或者expression
一个异步的method仅支持以下返回值:
Task
Task // 泛型
void // 定义void返回仅仅是为了跟普通函数保持兼容
任何包含可访问GetAwaiter()方法的类型(从C# 7.0开始支持)
Async方法在执行的时候,开始是以同步的方式执行(即在调用方的thread里跑的),直到遇到await关键字,从await关键字开始,C#会另起一个thread来执行await后面的代码。
如果Async方法里面的代码没有包含await的代码会怎么样?那整个函数就会同步执行,跟普通函数没差别。编译器也会给你个警告。
话不多说,上代码
using System;
using System.Threading;
public class AsyncAwaitTest
{
static void Main(string[] args)
{
Console.WriteLine("Main start, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
var teaResult = PrepareteaAsync();
var hotWaterResult = PrepareHotWaterAsync();
var cupResult = PrepareCupAsync();
Console.WriteLine("Main waiting, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
teaResult.GetAwaiter().GetResult();
hotWaterResult.GetAwaiter().GetResult();
cupResult.GetAwaiter().GetResult();
Console.WriteLine($"All Done! tea={teaResult.Status}, hotWater={hotWaterResult.Status}, cup={cupResult.Status}, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("Main end, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
}
private static async Task PrepareteaAsync()
{
Console.WriteLine("PrepareteaAsync start, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
await TimeConsumingMethod(3000);
Console.WriteLine("PrepareteaAsync end, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
}
private static async Task PrepareHotWaterAsync()
{
Console.WriteLine("PrepareHotWaterAsync start, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
await TimeConsumingMethod(5000);
Console.WriteLine("PrepareHotWaterAsync end, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
}
private static async Task PrepareCupAsync()
{
Console.WriteLine("PrepareCupAsync start, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
await TimeConsumingMethod(2000);
Console.WriteLine("PrepareCupAsync end, Thread ID is " + Thread.CurrentThread.ManagedThreadId);
}
static Task<int> TimeConsumingMethod(int timeCost)
{
var task = Task.Run(() => {
Console.WriteLine($"TimeconsumingMethod({timeCost}) start with Thread ID is {Thread.CurrentThread.ManagedThreadId}");
Thread.Sleep(timeCost);
Console.WriteLine($"TimeconsumingMethod({timeCost}) end with Thread ID is {Thread.CurrentThread.ManagedThreadId}");
return timeCost;
});
return task;
}
}
运行结果如下:
Main start, Thread ID is 1
PrepareteaAsync start, Thread ID is 1
PrepareHotWaterAsync start, Thread ID is 1
PrepareCupAsync start, Thread ID is 1
Main waiting, Thread ID is 1
TimeconsumingMethod(3000) start with Thread ID is 4
TimeconsumingMethod(2000) start with Thread ID is 7
TimeconsumingMethod(5000) start with Thread ID is 6
TimeconsumingMethod(2000) end with Thread ID is 7
PrepareCupAsync end, Thread ID is 7
TimeconsumingMethod(3000) end with Thread ID is 4
PrepareteaAsync end, Thread ID is 4
TimeconsumingMethod(5000) end with Thread ID is 6
PrepareHotWaterAsync end, Thread ID is 6
All Done! tea=RanToCompletion, hotWater=RanToCompletion, cup=RanToCompletion, Thread ID is 1
Main end, Thread ID is 1
代码解析
上面用泡茶的例子来解析,简单拆解为3个步骤:准备茶叶,烧开始,洗杯子,三个步骤在Main函数都是并行的,最后等待三者的结果
可以看到PrepareteaAsync(), PrepareHotWaterAsync(), PrepareCupAsync() 三个函数的第一行log都是在thread ID为1的进程打印的,而后面的部分都是在各自的子线程里打印出来的。理解这点很重要。
在三个异步线程启动完成后,我们还需要等待返回结果。如果这个时候主线程退出的话,那子线程也会随之退出的。在刚开始写Console Application的测试code我就遇到这个问题,刚开始还加sleep来等,或者干脆写个ReadKey()等,其实用GetAwaiter()才是正解。
每个Async方法的返回其实是个Task,关于Task的更多属性可以参考后面的链接的内容。
参考链接:
Asynchronous programming in C# | Microsoft Docs
The Task Asynchronous Programming (TAP) model with async and await (C#)" | Microsoft Docs
Async and Await In C# (c-sharpcorner.com)
Understanding async / await in C# - Stack Overflow
Async and Await (stephencleary.com)