C# 之 Async/Await

转载地址: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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值