一、什么是异步编程
对于异步编程这一概念,可以用餐厅点餐为例。餐厅点餐的同时服务员在身旁等待称之为“同步点餐”。而自己点餐的同时服务员去照顾其他客人,等自己点完后再喊服务员则是“异步点餐”。
异步点餐
优点:可以提高服务器接待请求的数量
缺点:不会使单个请求的处理效率变高,甚至可能略有降低
二、学会使用await、async
用async关键字修饰方法后,这个方法就成为了异步方法,但要注意以下几点:
(1)异步方法的返回值一般是Task<T>泛型类型
(2)按照约定,异步方法以Async结尾,虽不是语法强制,但是能让开发人员一眼看出是异步方法
(3)如果方法没有返回值最好也声明为非泛型的Task类型,不建议使用void
(4)调用泛型方法的时候,一般在方法前面加await关键字,这样方法调用的返回值就是泛型指定的T类型的值
(5)一个方法如果有await调用,该方法也应修饰为async
代码演示如下:
Console.WriteLine("写入文件前");
await File.WriteAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt","hello async");
Console.WriteLine("读取文件前");
string s = await File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt");
Console.WriteLine(s);
await关键字的意思:调用异步方法,等异步方法结束后再继续向下执行。同时await可以直接把返回数据直接从<Task>中提取出来
其实只要方法返回值是Task或者Task<T>类型,我们就可以用await关键字进行调用。对于调用者而言,被调用方法是否被修饰为async没有区别。修饰为async只是为了在方法内使用await调用。
提醒:c# 9.0中新增了顶级语句允许直接在入口代码使用await关键字,如果不使用顶级语 句,需要用async修饰Main方法,然后把返回值修改为Task类型。
2.1async、await原理深度解析
结论:编译器会把async方法编译成一个大类,并且把异步方法的代码切分成多次方法调用。
验证如下:
Thread.CurrentThread.ManagedThreadId用于获取当前线程ID,从上可以很清晰的看出前面三个线程都不同,但是第四个用同步进行调用的时候线程是相同的。
结论: 异步方法在进行await调用的等待期间,框架会把当前线程返回线程池,等异步方法调用执行完毕后,框架会从线程池再取出一个线程,以执行后续的代码。
2.2异步方法不等于多线程
很多人可能认为异步方法中的代码一定是在新线程中执行,所以和多线程划上等号。其实异步方法中的代码并不会自动在新线程执行,除非把代码放在新线程中执行。
验证如下:
结论:异步方法代码不会自动在新线程中执行
可以把要执行的代码以委托的形式传递给Task.Run,这样就从线程池中取出一个线程执行我们的代码。
三、异步编程的几个重要问题
(1).NET Core的类库已经全面拥抱异步了,但是有些类为了兼容旧API,也提供了非异步方法。建议只使用异步方法。
(2)如果由于框架问题,方法无法标注为async,那么就无法使用await调用异步方法。对于返回值是Task<T>可以调用Result属性或者GetAwaiter().GetResult来等待异步结束执行获取返回值;对于返回值是Task类型可以在Task对象上调用wait方法调用异步方法并等待任务执行结束。(非特殊情况不建议使用,会阻塞线程的调用)
string s1 = File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt").Result;
string s2 = File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt").GetAwaiter().GetResult();
File.WriteAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt", "hello").Wait();
(3)异步暂停方法。如果需要异步方法暂停一段时间再执行,使用await Task.Delay();
(4)使用CancellationToken类型的对象可以让异步方法提前终止。
(5)可以使用Task.WhenAll同时等待多个Task的执行结束。Task.WhenAny方法用于等待多个任务,只要其中一个任务完成,代码就会继续向下执行。
Task<string> t1 = File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/1.txt");
Task<string> t2 = File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/2.txt");
Task<string> t3 = File.ReadAllTextAsync("D:/桌面文件/c#/csdn博客/3.txt");
string[] results = await Task.WhenAll(t1,t2, t3);
string s1 = results[0];
string s2 = results[1];
string s3 = results[2];
(6)接口或者抽象类方法不能修饰为async,但是返回值可以设置为Task类型,在实现类中再根据需要为方法添加async进行修饰。