异步编程理解
1.async await 原理
把异步方法解析成一个类,把内部异步函数分块,通过操作状态机,排布执行计划。
线程使用:await调用的等待期间,.NET会把当前线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池再取一个线程执行后续代码,即await不会阻塞线程,await函数执行完成会回调线程池唤起新线程拿到返回结果。(Thread.CurrentThread.ManagedThreadId获取当前线程Id)
测试线程调度code:
Console.WriteLine(Thread.CurrentThread.ManagedThreadId); string fileName = @"C:\Users\qwei\source\1.txt"; StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 10000; i++) { stringBuilder.Append(i.ToString() + " helloya"); } await File.WriteAllTextAsync(fileName, stringBuilder.ToString()); Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
result:
2.异步编程不等于多线程编程
异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。
放到新线程执行code:
internal class Program { static async Task Main(string[] args) { Console.WriteLine("before," + Thread.CurrentThread.ManagedThreadId); double a = await CalcAsync(5000); Console.WriteLine(a.ToString()); Console.WriteLine("after," + Thread.CurrentThread.ManagedThreadId); } static async Task<double> CalcAsync(int num) { Console.WriteLine("CalcAsyncBefore," + Thread.CurrentThread.ManagedThreadId); return await Task.Run(() => { Console.WriteLine("CalcAsync," + Thread.CurrentThread.ManagedThreadId); double result = 0; Random random = new Random(); for (int i = 0; i < num * num; i++) { result += random.NextDouble(); } return result; }); } }
result:
3.没有async的异步方法
Q:什么情况用async呢?
A:如果一个异步函数只是对别的异步函数调用的转发,并没有太多复杂的逻辑,那么可以去掉async关键字;若需要对内部调用的异步函数结果做处理的情况,建议使用async。
返回值为Task的不一定都要标注async,标注async可以让我们可以更方便的await,举例如下:
///直接转发内部调用的异步方法结果。使用async,多拆装一次 static async Task<string> ReadAsync(int num) { switch (num) { case 0: string s = await File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt"); return s; case 1: string s1 = await File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt"); return s1; default: throw new ArgumentException(); } } ///直接转发内部调用的异步方法结果,不使用async static Task<string> ReadWithoutAsync(int num) { switch (num) { case 0: return File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt"); case 1: return File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt"); default: throw new ArgumentException(); } } ///需要对调用的异步方法做处理,则需要使用async static async Task<string> ReadAsyncNew(int num) { switch (num) { case 0: string s = await File.ReadAllTextAsync(@"C:\Users\qwei\source\1.txt"); return s + "addtional"; case 1: string s1 = await File.ReadAllTextAsync(@"C:\Users\qwei\source\2.txt"); return s1 + "addtional"; default: throw new ArgumentException(); } }
async方法缺点:
1.异步方法会生成一个类,运行效率没有普通方法高;
2.可能会占用非常多的线程;
4.异步编程不要用sleep
Thread.Sleep()会阻塞调用线程,在异步方法中可能会阻塞主线程,建议使用await Task.Delay()
样例code:
internal class Program { static async Task Main(string[] args) { Console.WriteLine("Hello World!"); Console.WriteLine(await DownloadHTML(2, "https://www.baidu.comsssssss")); } public static async Task<string> DownloadHTML(int retryCount, string webUrl) { using (HttpClient client = new HttpClient()) { try { string strWeb = await client.GetStringAsync(webUrl); return strWeb; } catch (Exception ex) { Console.WriteLine("下载失败,重试中"); retryCount--; if (retryCount == 0) { throw new Exception("下载失败"); } await Task.Delay(500); return await DownloadHTML(retryCount, webUrl); } } } }
5.CancellationToken
使用场景:
需要提前终止任务的场景,比如:请求超时、用户取消请求 |
cancellationToken 结构体 重点属性:
None:空 |
bool IsCancellationRequested : 是否取消 |
ThrowIfCancellationRequested() : 如果任务被取消,执行到这句话是抛出异常 |
为下载一个网站N次添加CancellationToken,样例code:
internal class Program { static async Task Main(string[] args) { CancellationTokenSource cts= new CancellationTokenSource(); cts.CancelAfter(TimeSpan.FromSeconds(5)); CancellationToken ct= cts.Token; await Download2Async("https://www.baidu.com", 100, ct); await Download2OtherAsync("https://www.baidu.com",100,ct); } ///终止下一次请求 static async Task Download2Async(string url, int n, System.Threading.CancellationToken cn) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { string html= await client.GetStringAsync(url); if (!string.IsNullOrEmpty(html)) { Console.WriteLine($"{DateTime.Now}:{html.Substring(10)}"); } //func 1: /*if (cn.IsCancellationRequested) { Console.WriteLine("request had been canceled"); break; }*/ //func 2: cn.ThrowIfCancellationRequested(); } } } //请求后终止 static async Task Download2OtherAsync(string url, int n, System.Threading.CancellationToken ct) { using (HttpClient client = new HttpClient()) { for (int i = 0; i < n; i++) { var response = await client.GetAsync(url, ct); string html = await response.Content.ReadAsStringAsync(); Console.WriteLine($"{DateTime.Now}:{html.Substring(20)}"); } } } }
6.Task类重要函数 WhenAny和WhenAll
WhenAny:(IEnumerable<Task> tasks),任何一个Task完成,Task就完成; |
WhenAll:(IEnumerable<Task> tasks),所有Task完成,Task才完成,多用于等待多个任务执行结束,但是不在乎他们的执行顺序的场景; |
FromResult()创建普通数值的Task对象; |
获取某个文件夹内所有文件长度总和,样例code:
internal class Program { static async Task Main(string[] args) { string[] files = Directory.GetFiles(@"C:\NetCore\temp"); Task<int>[] countsTask=new Task<int>[files.Length]; for (int i = 0; i < files.Length; i++) { string filename=files[i]; Task<int> t = ReadCharsCount(filename); countsTask[i] = t; } int[] counts =await Task.WhenAll(countsTask); int C = counts.Sum(); Console.WriteLine(C); } private static async Task<int> ReadCharsCount(string filename) { string s = await File.ReadAllTextAsync(filename); return s.Length; } }
7.异步编程其他问题
a. interface中方法或者抽象方法不能修饰为async(在实现类中加async);
b. yield return不仅能够简化数据返回,而且可以让数据处理”流水线化“,提升性能;
------------------------完结~~撒花~~~❀❀❀❀❀❀❀------------------------