异步编程 async/await
异步编程可以提高整个系统的响应速度,但不能提高单次请求的时间。
服务器可以响应更多的请求,每一个请求的总时长基本是不变的。
当一个请求A处理比较耗时的操作,系统就会不等这个请求A,让A自己处理这个耗时操作,而是直接处理下一个请求B。
等这个请求A中耗时的操作处理完了,系统再来接着处理请求A后续的功能。
Async/Await 是一个语法糖,方便写异步编程,其原理是状态机模式编程。
举了一个点菜的例子:你进一个菜馆,服务员A,来接待你到指定座位,然后你开始点菜,由于你点菜比较耗时,服务员A给了你菜单后就去服务其他客户去了。
等你好久点完菜了,然后你再叫服务员把菜单给后厨,但这时服务你的不一定是A,很可能是服务员B。
static async Task Main(string[] args) { Console.WriteLine("第一:"+Thread.CurrentThread.ManagedThreadId); string filePath = @"D:\GitSource\Temp\a\1.txt"; StringBuilder sb = new StringBuilder(); for (int i = 0; i < 10000; i++) { sb.Append("Hello World!"); } Console.WriteLine("第二:" + Thread.CurrentThread.ManagedThreadId); await File.WriteAllTextAsync(filePath, sb.ToString()); Console.WriteLine("第三:" + Thread.CurrentThread.ManagedThreadId); string s = await File.ReadAllTextAsync(filePath); Console.WriteLine("第四:" + Thread.CurrentThread.ManagedThreadId); Console.WriteLine(s); Console.WriteLine("guala"); Console.WriteLine("第五:" + Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); }
可以看到打印出来的线程Id是不一样的。 如果异步处理的时间比较短,也可能一样。
异步方法返回一般是Task<T>类型(无返回值时是Task),当你用await来等时,取到的结果就是T 对象本身。类似于Task.Result
异步不等于多线程
方法标了Async Task就一定是异步方法,但不一定是多线程方法,比如:
Console.WriteLine("异步前:"+Thread.CurrentThread.ManagedThreadId); double result =await GetResultAsync(5); Console.WriteLine("异步后:" + Thread.CurrentThread.ManagedThreadId); Console.ReadKey(); async Task<double> GetResultAsync(int input) { Console.WriteLine("异步中:" + Thread.CurrentThread.ManagedThreadId); double result = 0; for (int i = 0; i < input * input; i++) { result += i; } return result; //return await Task.Run( () => //{ // double result = 0; // for (int i = 0; i < input * input; i++) // { // result += i; // } // return result; //}); }
运行结果:
如果该方法中使用Task来运行:
可以看出是真的多线程执行。
线程休息 ,间隔几秒再做某事,用 await Task.Delay(),不阻塞主线程,异步调用,不要用Thread.Sleep(),这个会阻塞主线程 ,影响web服务的性能
异步调用时注意参数:CancellationToken ,可以传递参数,适时停止操作
static async Task Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); cts.CancelAfter(1000);//1秒之后取消 await GetWebSitHtml("http://www.baidu.com", 10, cts.Token); Console.ReadKey(); } async static Task GetWebSitHtml(string url,int n,CancellationToken cancellationToken) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { string html = await client.GetStringAsync(url); Console.WriteLine(html); if(cancellationToken.IsCancellationRequested) { Console.WriteLine("请求被取消了"); break; } } } }
如果调用的异步方法本身支持 CancellationToken,则记得要传递该参数给异步方法。
GetStringAsync 本身不支持 CancellationToken,可以换一个支持的
async static Task GetWebSitHtml(string url,int n,CancellationToken cancellationToken) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { var result = await client.GetAsync(url,cancellationToken); string html =await result.Content.ReadAsStringAsync(); Console.WriteLine(html); if(cancellationToken.IsCancellationRequested) { Console.WriteLine("请求被取消了"); break; } } } }
微软提供的这个异步方法支持 cancellationToken 参数,我们只需要接过参数传给它就行了。 这个方法里也随时停止。
再有一种是手动停止:比如输入q字母停止:
static async Task Main(string[] args) { CancellationTokenSource cts = new CancellationTokenSource(); GetWebSitHtml("http://www.baidu.com", 100, cts.Token); while (Console.ReadLine() !="q") { } cts.Cancel(); Console.ReadKey(); } async static Task GetWebSitHtml(string url,int n,CancellationToken cancellationToken) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { await Task.Delay(1000);//休息一下 要不来不及输入q var result = await client.GetAsync(url,cancellationToken); string html =await result.Content.ReadAsStringAsync(); Console.WriteLine(html); if(cancellationToken.IsCancellationRequested) { Console.WriteLine("请求被取消了"); break; } } } }
杨中科讲异步编程,将的很透彻,耐心看完 .NET 6教程,.Net Core 2022视频教程,杨中科主讲_哔哩哔哩_bilibili