- 异步和多线程不是同一个概念,异步相当于餐厅服务员给你递给菜单,然后剩下的你自己看菜单,看完再叫服务员(服务员只负责给客人递菜单,递完一个到下一个那边),而多线程相当于n个服务员对应n个客人,一对一服务。
- 异步方法具有传染性,await会阻塞当前异步线程,直到异步方法执行完毕才开始执行后续的代码逻辑。
- 不写async的方法 有返回值:异步方法().Result 无需写await了手动取值无返回值:异步方法().Wait() 容易造成死锁,谨慎使用
- 异步Lambda表达式 在obj前面加上async,在表达式前面加上async。
- await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。
- 如果一个异步方法只是对别的异步方法的调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字。
- 利用await Task.Delay(TimeSpan.FromSeconds(2))来替代Thread.Sleep()
- 在ASP.NET开发中,只要有cancellationtoken参数就传入。Action方法都传入CancellationToken token参数,调用异步方法能传token都传这个参数。
- Task.WhenAll Task.WhenAny
- yield return和异步 async IAsyncEnumerable
返回值 MethodName() 调用的时候await foreach一 基础概念
异步方法”:用async关键字修饰
1.返回值一般是TaskT是真正的返回值类型,异步方法名字以Aysnc结尾
2.即使方法没有返回值,也最好把返回值申明为非泛型的Task
3.调用泛型方法时,一般在方法前加上await,这样拿到的返回值就是泛型指定的T类型。
4.异步方法具有传染性:一个方法中如果有await调用,则这个方法必须修饰为async。
主函数Main里面调用了异步方法,void必须更改为Task,如下:
await可以将返回值从Task
static async Task Main(string[] args){//string fileName = @"E:\albert.txt";//File.WriteAllText(fileName, "Hello");//string s = File.ReadAllText(fileName);//Console.WriteLine(s);string fileName = @"E:\albert.txt";await File.WriteAllTextAsync(fileName, "Albert");//这里面加了await把Task<string>中的返回值取出来了/** 第二种写法:Task<string> t = File.ReadAllTextAsync(fileName);string s = await t;**/string s = await File.ReadAllTextAsync(fileName);Console.WriteLine(s);Console.ReadLine();}
二 异步方法(Self Create)
HttpClient最好和HttpFactory一起使用
HttpClient实现了IDisposable所以需要写using进行回收
static async Task Main(string[] args){string filename = @"E:\albert.txt";await DownloadHtmlAysnc("http://www.baidu.com",filename);}/// <summary>/// 从某个网站下载html到文件中/// </summary>/// <param name="url"></param>/// <param name="filename"></param>/// <returns></returns>static async Task DownloadHtmlAysnc(string url,string filename){//最好和HttpFactory一起使用//HttpClient实现了IDisposable所以需要写using进行回收using (HttpClient httpClient = new HttpClient()){string html = await httpClient.GetStringAsync(url);await File.WriteAllTextAsync(filename, html);}}
三 如果调用方不支持异步方法
有返回值:异步方法().Result 无需写await了手动取值
无返回值:异步方法().Wait() File.WriteAllTextAsync(path,”Albert”).Wait();
容易造成死锁,谨慎使用
//手动取值string s2 = File.ReadAllTextAsync(fileName).Result;//Wait(),无返回值的情况File.WriteAllTextAsync(path,"Albert").Wait();
四 异步委托:异步Lambda表达式
在委托中使用,异步Lambda表达式 在obj前面加上async。
//异步Lambda表达式ThreadPool.QueueUserWorkItem(async obj =>{await File.WriteAllTextAsync(fileName, "xxxx");});
五 Async Await原理揭秘
using System;using System.Net.Http;using System.Threading.Tasks;using System.IO;static async Task Main(string[] args){using(HttpClient httpClient = new HttpClient()){string html = await httpClient.GetStringAsync("http://www.google.com");Console.WriteLine(html);}string txt = "Hello ALbert";string filename = @"E:/albert.txt";await File.WriteAllTextAsync(filename,txt);Console.WriteLine("写成功");string s = await File.ReadAllTextAsync(filename);Console.WriteLine("读成功”);}
语法糖,最终编译成状态机的调用。async方法会被C#编译器编译成一个类,会根据await调用进行切分为多个状态,对async方法的调用会被拆分为对MoveNext的调用。
六 Async Await背后的线程调度
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。(未必是同一个线程)Tread.CurrentThread.ManagedThreadID
很多个客户点菜,给你递菜单的服务员等你点好呼叫服务员,可能是之前那个,也可能不是。而不需要像多线程那样每个客人对应一个服务员。线程可以得到充分的利用。到要等待的时候,如果发现已经执行结束了,就没必要切换线程浪费开销了。
异步执行的代码并不自动在新线程中执行,除非手动把代码放到新线程中执行。Task.Run(()=>{});可以切线程。
static async Task<double> ReadRandom(){//同步写法://Console.WriteLine($"我是主线程:{Thread.CurrentThread.ManagedThreadId}");//double s = new Random().NextDouble();//return s;//使用await Task.Run(()=>{})开启异步,跨线程池//自动匹配返回值类型return await Task.Run(() => {Console.WriteLine($"我是主线程:{Thread.CurrentThread.ManagedThreadId}");double s = new Random().NextDouble();return s;});}

七 无async的异步方法
async把返回值包装成Task
async会生成一个类,然后通过状态机MoveNext()进行状态的切换,可能造成线程切换和资源消耗。如果在有必要情况下,不写async。不做拆完后再装的操作。只是一个普通的方法调用。性能效率更高,不会造成线程资源的浪费。
如果一个异步方法只是对别的异步方法的调用的转发,并没有太多复杂的逻辑(比如等待A的结果,再调用B;把A调用的返回值拿到内部做一些处理再返回),那么就可以去掉async关键字。
static async Task<string> ReadFileAsync(int num){switch (num){//ReadAllTextAsync返回Task<string> await将string值取出来,返回给调用//函数ReadFileAsync,ReadFileAsync通过async将string包装成Task<string>case 1:return await File.ReadAllTextAsync(@"E:\albert.txt");case 2:return await File.ReadAllTextAsync(@"E:\2.txt");default:throw new ArgumentException("num invalid");}}static Task<string> ReadFileSecondAsync(int num){switch (num){//ReadAllTextAsync返回Task<string> await将string值取出来,返回给调用//函数ReadFileAsync,ReadFileAsync通过async将string包装成Task<string>case 1:return File.ReadAllTextAsync(@"E:\albert.txt");case 2:return File.ReadAllTextAsync(@"E:\2.txt");default:throw new ArgumentException("num invalid");}}
八 不要Thread.Sleep()
利用await Task.Delay(TimeSpan.FromSeconds(2))来替代Thread.Sleep()
Thread.Sleep()会阻塞当前线程
await Task.Delay()方式可以有效减少线程的消费,是创建高伸缩性的服务器程序的关键。
在winform程序中,点击事件可以改写成异步方法,但是void不需要变为Task,因为那是事件(委托)规定好的
Button_Click不是在主线程中,而是异步线程,如果用的是Thread.Sleep()则会阻塞主线程
await Task.Sleep()阻塞的是异步线程。Sleep会阻塞Web服务器线程,线程会卡住。
private async void Button1_Click(object sender,EventArgs e){using(HttpClient httpClient = new HttpClient()){string s1 = await httpClient.GetStringAsync("http://www.baidu.com");this.textbox1.Text = s1.Substring(0,20);//Thread.Sleep(3)阻塞的是主线程await Task.Delay(TimeSpan.FromSeconds(3));//阻塞的是MoveNext切状态string s2 = await httpClient.GetStringAsync("http://www.baidu.com");this.textbox1.Text = s2.Substring(0,20);}}
九 CancellationToken
在ASP.NET开发中,只要有cancellationtoken参数就传入。Action方法都传入CancellationToken token参数,调用异步方法能传token都传这个参数。
namespace _210712_Demo01_Aysnc{class Program{static async Task Main(string[] args){CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();cancellationTokenSource.CancelAfter(5000);await DownloadHtml("https://www.baidu.com", 10000, cancellationTokenSource.Token);}static async Task DownloadHtml(string url,int downloadCtn,CancellationToken cancellationToken){using(HttpClient httpClient = new HttpClient()){for (int i = 0; i < downloadCtn; i++){string html = await httpClient.GetStringAsync(url);Console.WriteLine($"{DateTime.Now}:{html}");//第二种方式html内容的方式,可能在5s没到就取消请求了,而不需要一直等到请求后才能处理var resp = await httpClient.GetAsync(url, cancellationToken);string htmlTwo = await resp.Content.ReadAsStringAsync();//此种方案无法实现立即取消//if (cancellationToken.IsCancellationRequested)//{// Console.WriteLine("请求被取消");// break;//}}}}}}public async Task<IActionResult> Index(CancellationToken token){await DownloadAsync("https://www.baidu.com", 10000, token);return View();}
十 Task重要方法
Task.WhenAny(IEnumerable
Task.WhenAll(params Task
//开启n个Task,然后将Task放在Task数组中,用WhenAll等待所有数组Task执行完毕
namespace _20170713_Demon01_TaskWhenAll{class Program{static async Task Main(string[] args){string[] filename = Directory.GetFiles(@"C:\01_AlbertCode\NET\NETCoreWinform");Task<int>[] countTask = new Task<int>[filename.Length];//开启n个Task,然后将Task放在Task数组中,用WhenAll等待所有数组Task执行完毕for (int i = 0; i < filename.Length; i++){string file = filename[i];Task<int> t = ReadCharCount(file);countTask[i] = t;}int[] counts = await Task.WhenAll(countTask);Console.WriteLine(counts.Sum());//计算数组的和 LinqConsole.ReadLine();}//将文件夹下所有文件的字符个数汇总出来static async Task<int> ReadCharCount(string filename){string s = await File.ReadAllTextAsync(filename);return s.Length;}}}
FromResult()创建普通数值的Task对象:异步方法中在没有声明async的情况下使用:(容易造成死锁)
//手动取值string s2 = File.ReadAllTextAsync(fileName).Result;//Wait(),无返回值的情况File.WriteAllTextAsync(path,"Albert").Wait();
十一 零碎知识点
- 接口/抽象类的方法不能修饰为async
- 异步与yield yield return不仅能够简化数据的返回,而且可以让数据处理“流水线化”,提升性能。yield return按需供给 yield return 会再调用的时候才会供给,反编译拆成三段。在旧版,异步方法无法用yield。从C#8.0开始,把返回值声明为IAsyncEnumerable(不要带Task),然后遍历的时候用await foreach()即可。
```csharp
//两者等价,第一种方法需要额外开辟空间,而第二个则按需供给。
static IEnumerable
Test(){ List list = new List (); list.Add(“Hello”); list.Add(“Yzk”); list.Add(“Youzack”); }
static IEnumerable
static IAsyncEnumerable
static async Task Main(string[] args){ await foreach(var s in Test()){ Console.WriteLine(s); } } ```
- ASP.NET Core和控制台项目中没有SynchronizationContext,因此不用管ConfigureAwait(false)等。不要同步异步混用。在Winform和WPF中依旧存在。
十二 疑惑点
1.异步方法可以重载吗?
2.如何避免不支持异步方法调用方造成的死锁?
3.状态机模式的理解?
4.为什么要把一个async方法拆分为多个状态然后分为多次调用?
5.异步的可以避免线程等待耗时操作但是await还是等待呀?
await调用的等待期间,.NET会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取出来一个线程执行后续代码。(未必是同一个线程)Tread.CurrentThread.ManagedThreadID
很多个客户点菜,给你递菜单的服务员等你点好呼叫服务员,可能是之前那个,也可能不是。而不需要像多线程那样每个客人对应一个服务员。线程可以得到充分的利用。到要等待的时候,如果发现已经执行结束了,就没必要切换线程浪费开销了。
