来自《.NET 6教程,.Net Core 2022视频教程,杨中科主讲》的笔记
一开始觉得为啥上来就讲异步,听完你会发现,这将影响你的编程书写习惯,大牛就是牛
P10异步编程的形象比喻(餐厅点餐)
异步编程:不等待,提高处理请求的数量(接待能力),但是单个请求处理速度不会有提升(提升并发);你处理完了我再回来处理(你点餐完成后我再过来取走菜单)
async\await简化了多线程开发,但是不等于“多线程”
P11使用异步方法
有返回值写Task<int>,无返回值写Task,不要写Void
如果一个异步方法的返回值是这个样子:Task<string> ReadAllTextAsync(string path, CancellationToken cancellationToken = default)
那么可以在调用的时候直接使用 string s= await File.ReadAllTextAsync(...);调用时使用await关键字,会直接得到 string 类型的值;
或者使用
Task<string> t= File.ReadAllTextAsync(...); string s=await t;
贴代码,我这里使用是Net6
// See https://aka.ms/new-console-template for more information using System.Text; string filename = @"D:\a\1.txt"; /* 同步方法 File.WriteAllText(filename, "hello"); string s= File.ReadAllText(filename); */ StringBuilder sb=new StringBuilder(); for(int i = 0; i < 10000; i++) { sb.AppendLine(i.ToString()); } await File.WriteAllTextAsync(filename, sb.ToString());//测试:不加await时会报错,因为文件以独占方式写入,后面程序不能读取,加await关键字后,后面程序会等待文件写完再读取 //await等待写完再继续执行 string s = await File.ReadAllTextAsync(filename); /* Task<string> t = File.ReadAllTextAsync(filename); string sa = await t; */ Console.WriteLine(s); Console.ReadLine();
P12:编写异步方法
上代码
// See https://aka.ms/new-console-template for more information await DownloadHtmlAsync("http://www.baidu.com", @"D:\a\2.txt"); int l= await DownloadHtml2Async("http://www.baidu.com", @"D:\a\3.txt"); Console.WriteLine("OK"+l); Console.ReadLine(); //无返回值 static async Task DownloadHtmlAsync(string url, string filename) { using (var client = new HttpClient()) { var response = await client.GetStringAsync(url); await File.WriteAllTextAsync(filename, response); } } //带返回值 static async Task<int> DownloadHtml2Async(string url, string filename) { using (var client = new HttpClient()) { var response = await client.GetStringAsync(url); await File.WriteAllTextAsync(filename, response); return response.Length; } }
对于不支持异步的地方,可以使用Result、wait()
逼不得已再这样使用,否则某天程序卡顿了可以考虑排查一下这样的代码
//只能使用同步调用的地方可以这样写(异步转同步) //带返回值写法 Task<string> t = File.ReadAllTextAsync(@"D:\a\3.txt"); string s=t.Result; //或 string ss=File.ReadAllTextAsync(@"D:\a\3.txt").Result; //无返回值写法 File.WriteAllTextAsync(@"D:\a\3.txt","aaaaaaaaaaaaaaaaaaa").Wait();
委托中调用异步方法,一样的传染方式
// See https://aka.ms/new-console-template for more information ThreadPool.QueueUserWorkItem(async (obj) => { while (true) { Console.WriteLine("XXXXXXXXXXXXXXX"); await File.WriteAllTextAsync(@"D:\a\3.txt", "aaaaaaaaaaaaaaaaaaa"); } }); Console.ReadLine();
P13 async、await原理揭秘(大体能看懂就行)
上菜
// See https://aka.ms/new-console-template for more information using(HttpClient http=new HttpClient()) { string html = await http.GetStringAsync("http://www.baidu.com"); Console.WriteLine(html); } string txt = "hello aaa"; string filename = @"D:\a\4.txt"; await File.WriteAllTextAsync(filename, txt); Console.WriteLine("写入成功"); string aaa=await File.ReadAllTextAsync(filename); Console.WriteLine(aaa); Console.ReadLine();
反编译软件:ILSpy,反编译DLL文件,降低C#版本
等待的过程中其实是处理其他业务去了(干别的去了)
P14:async背后的线程切换
比喻:接待进入餐厅的服务员(线程)和点餐完成后取菜单的服务员不一定是一个人(同一线程)
上菜:
// See https://aka.ms/new-console-template for more information using System.Text; Console.WriteLine(Thread.CurrentThread.ManagedThreadId); StringBuilder sb=new StringBuilder(); for(int i=0; i < 10000; i++) { sb.AppendLine("AAAAAAAAAAAAAAAAAAAAAa"); } await File.WriteAllTextAsync(@"D:\a\5.txt",sb.ToString());//还有一个细节:调用的异步方法(WriteAllTextAsync)包含线程切换的代码,所以才会切换线程,后面会讲到
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); Console.ReadLine();
调节循环次数观察结果(取决于CLR和电脑性能)
其实最好是避免线程切换;
P15:异步方法不等于多线程
上菜:
// See https://aka.ms/new-console-template for more information Console.WriteLine("前"+Thread.CurrentThread.ManagedThreadId); double vx= await CalcAsync(1000); Console.WriteLine(vx); Console.WriteLine("后" + Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); static async Task<double> CalcAsync(int n) { Console.WriteLine("Calc:"+Thread.CurrentThread.ManagedThreadId); double result = 0; Random random = new Random(); for(int i = 0; i < n*n; i++) { result+=Math.Round(random.NextDouble() * n); } return result; }
线程切换的写法
// See https://aka.ms/new-console-template for more information Console.WriteLine("前"+Thread.CurrentThread.ManagedThreadId); double vx= await CalcAsync(1000); Console.WriteLine(vx); Console.WriteLine("后" + Thread.CurrentThread.ManagedThreadId); Console.ReadLine(); static async Task<double> CalcAsync(int n) { /* Console.WriteLine("Calc:"+Thread.CurrentThread.ManagedThreadId); double result = 0; Random random = new Random(); for(int i = 0; i < n*n; i++) { result+=Math.Round(random.NextDouble() * n); } return result; */ return await Task.Run(() => { Console.WriteLine("Calc:" + Thread.CurrentThread.ManagedThreadId); double result = 0; Random random = new Random(); for (int i = 0; i < n * n; i++) { result += Math.Round(random.NextDouble() * n); } return result;//从这里推断返回值,无返回值的上面就不用写return了,直接await就行 }); }
P16:为什么有点异步方法没标ASYNC
// See https://aka.ms/new-console-template for more information string s = await ReadAsync(0); Console.WriteLine(s); Console.ReadLine(); /* static async Task<string> ReadAsync(int num) { string s=""; if(num== 0) { s= await File.ReadAllTextAsync(@"D:\a\a0.txt"); } else if (num == 1) { s = await File.ReadAllTextAsync(@"D:\a\a1.txt");; } else if (num == 2) { s = await File.ReadAllTextAsync(@"D:\a\a2.txt"); } return s; } */ //调用的返回值和自身的返回值一致,所以不需要Async 与 await了,此方法相当于做了个转发而已 static Task<string> ReadAsync(int num) { if (num == 0) { return File.ReadAllTextAsync(@"D:\a\a0.txt"); } else if (num == 1) { return File.ReadAllTextAsync(@"D:\a\a1.txt"); } else { return File.ReadAllTextAsync(@"D:\a\a2.txt"); } }
反编译后可以看到这只是一个方法,并没有重新生成一个类
static Task<double> Calc1Async(int n) { return Task.Run(() => { Console.WriteLine("Calc:" + Thread.CurrentThread.ManagedThreadId); double result = 0; Random random = new Random(); for (int i = 0; i < n * n; i++) { result += Math.Round(random.NextDouble() * n); } //return result;//从这里推断返回值,无返回值的上面就不用写return了,直接await就行 return Task.FromResult(result);//手动把double包装到Task,我怎么感觉两个return都一样 }); }
P17:不要用Sleep
Winform能直接看到效果(页面卡死)
Thread.Sleep(3000) 服务员卡主
await Task.Delay(3000);点餐人员卡主(休息一下再点)
private async void button1_Click(object sender, EventArgs e) { using(HttpClient client = new HttpClient()) { string s1 = await client.GetStringAsync("http://www.baidu.com/"); textBox1.Text = s1.Substring(0,20); //Thread.Sleep(3000);//阻塞当前线程,界面卡死 await Task.Delay(3000);// string s2 = await client.GetStringAsync("http://www.163.com/"); textBox2.Text = s2.Substring(0, 30); } }
P18:CancellationToken
// See https://aka.ms/new-console-template for more information CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); cancellationTokenSource.CancelAfter(5000);//五秒未执行完自动取消 CancellationToken ctn=cancellationTokenSource.Token; await Download3Async("https://www.baidu.com/", 200, ctn); Console.ReadLine(); static async Task DownloadAsync(string url,int n) { using (var client = new HttpClient()) { for (int i = 0; i < n; i++) { string html = await client.GetStringAsync(url); Console.WriteLine($"{DateTime.Now}:{html.Substring(0,20)}"); } } } static async Task Download2Async(string url, int n,CancellationToken cancellationToken) { using (var client = new HttpClient()) { for (int i = 0; i < n; i++) { string html = await client.GetStringAsync(url); Console.WriteLine($"{DateTime.Now}:{html.Substring(0, 20)}"); //方式一:手动相应取消,建议使用这种方式,思路更清晰 //if (cancellationToken.IsCancellationRequested) //{ // Console.WriteLine("请求被取消"); // break; //} //方拾贰:抛异常 cancellationToken.ThrowIfCancellationRequested(); //注意:以上方式虽然5s时发送了取消请求,但是仍要等待最后一次执行过程结束才能抛异常 } } } static async Task Download3Async(string url, int n, CancellationToken cancellationToken) { using (var client = new HttpClient()) { for (int i = 0; i < n; i++) { //优点:不同自己写程序处理,会及时终止程序 //缺点:是不能由用户控制,比如限定多少秒结束,而是由GetAsync内部实现的 //当然也可以他们处理,我们自己也处理 var responseMessage = await client.GetAsync(url, cancellationToken); string html = await responseMessage.Content.ReadAsStringAsync(); Console.WriteLine($"{DateTime.Now}:{html.Substring(0, 20)}"); //if (cancellationToken.IsCancellationRequested) //{ // Console.WriteLine("请求被取消"); // break; //} } } }
手动方式
手动处理异常
用户关闭网页后服务器也会及时终止请求运行的代码
ASP.NET CORE中几乎所有的异步方法都有CancellationToken 参数,能加的都加,能传的都传
上菜:
public async Task<IActionResult> Index(CancellationToken cancellationToken) { await DownloadAsync("https://www.baidu.com/", 10000, cancellationToken); return View(); } static async Task DownloadAsync(string url, int n, CancellationToken cancellationToken) { using (var client = new HttpClient()) { for (int i = 0; i < n; i++) { var responseMessage = await client.GetAsync(url, cancellationToken); string html = await responseMessage.Content.ReadAsStringAsync(); Debug.WriteLine(html); } } }
P19:WhenAll
例子:
Task<string> t1 = File.ReadAllTextAsync(@"D:\a\a1.txt");//这里并不使用await关键字 Task<string> t2 = File.ReadAllTextAsync(@"D:\a\a2.txt"); Task<string> t3 = File.ReadAllTextAsync(@"D:\a\a3.txt"); string[] strs= await Task.WhenAll(t1, t2, t3);//这里使用task的静态方法WhenAll,等待三个读取都结束后再继续执行 string s1=strs[0]; string s2=strs[1]; string s3=strs[2]; Console.WriteLine(s1); Console.WriteLine(s2); Console.WriteLine(s3);
string[] files= Directory.GetFiles(@"D:\a\"); Task<int>[] countTasks = new Task<int>[files.Length];//记录每个文件的字符数量 for(int i = 0; i < files.Length; i++) { string file=files[i]; Task<int> t = ReadCharCount(file); countTasks[i] = t; } int[] counts=await Task.WhenAll(countTasks); Console.WriteLine(counts.Sum());//计算数组和并打印,Linq下一节课讲 static async Task<int> ReadCharCount(string filename) { string s=await File.ReadAllTextAsync(filename); return s.Length; } Console.ReadLine();
P20:异步编程其他问题
接口中用Task定义,但是不能用async修饰
interface ITest { Task<int> GetCharCount(string file); } class Test : ITest { public async Task<int> GetCharCount(string filename)//根据情况使用async { string s = await File.ReadAllTextAsync(filename); return s.Length; } }
yield一个返回一次,相当于返回三次
foreach(var s in Test2()) { Console.WriteLine(s); } Console.ReadLine(); static IEnumerable<string> Test1() { List<string> list = new List<string>(); list.Add("hello"); list.Add("yzk"); list.Add("baidu"); return list; } //断点调试会发现他分三次返回,一个一个返回,而不是所有数据准备齐全以后再返回,流水化操作 static IEnumerable<string> Test2() { yield return "hello"; yield return "yzk"; yield return "google"; }
注意:不写Task
await foreach(var s in Test3())//注意要写await { Console.WriteLine(s); } Console.ReadLine(); static async IAsyncEnumerable<string> Test3()//不写Task { yield return "hello"; yield return "yzk"; yield return "google"; }
很少用。
这个不用研究了,Net5以后已经废了!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Blazor Hybrid适配到HarmonyOS系统
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· 分享4款.NET开源、免费、实用的商城系统
· 解决跨域问题的这6种方案,真香!
· 一套基于 Material Design 规范实现的 Blazor 和 Razor 通用组件库