C# 异步编程(async和await)
1. 源起
在我们从 .Net 转 .Net Core 的过程中,我们通常会查看一些源码或者看书进行学习,在其中你可能会看到类似于这样的代码
1 // 1. Asp.Net Core 的 控制器 类 2 [HttpGet] 3 public async Task<IActionResult> Index() 4 { 5 ...... 6 await ...... 7 ...... 8 } 9 10 // 2. Console 的 Program.cs 文件 11 static async Task Main(string[] args) 12 { 13 ...... 14 await ...... 15 ...... 16 }
这是一个很重要的 “新特性”---异步编程。在 C# 里面异步编程是通过关键字 async 和 await 两个关键字实现的。
那什么是异步编程?async、await是怎么样实现异步编程的呢?
2. 为什么要异步编程
在介绍异步编程之前,我们先说一下同步编程。
通常我们编写的代码在被执行时,通常是自上而下一行一行的执行,如过是执行方法,则进入方法内部继续一行一行执行。
这就是同步执行,也称之为同步编程(也可以说是编程)。
你会发现同步编程有一个问题,就是但是当一个方法执行耗时比较长时(例如读取文件内容),会阻塞下面的代码执行,整个软件都会进入等待状态(如是GUI界面,则执行操作的线程会阻塞,整个操作界面也会处于等待状态),体验度相当的不好---尤其是在计算机有多核的情况下,完全可以在另一个CPU上干其他的工作,同时计算机完成耗时任务的时候通知你。这就是异步编程的起源。更多示例
3. C# 异步编程
通过使用异步编程,你可以避免性能瓶颈并增强应用程序的总体响应能力。 但是,编写异步应用程序的传统技术可能比较复杂,使它们难以编写、调试和维护。
虽然 .Net 中有几种异步编程模式,但是目前建议使用的只有一种(TAP),我们将对历史有一些简单的说明,对于EAP进行更深入的介绍。
3.1 .Net 提供了3中异步编程模型(更多介绍)
- TAP(基于任务的异步模式):使用单一方法表示异步操作的开始和结束,是.Net中进行异步编程的推荐方法。(.Net Framework 4中引入的)
- EAP(基于事件的异步模式):提供异步行为的基于事件的旧模型。 这种模式需要后缀为 Async 的方法,以及一个或多个事件、事件处理程序委托类型和 EventArg 派生类型。 EAP 是在 .NET Framework 2.0 中引入的。 不建议新的开发使用此模式。
- APM(异步编程模型):(也称为 IAsyncResult 模式),这是使用 IAsyncResult 接口提供异步行为的旧模型。 在这种模式下,同步操作需要 Begin 和 End 方法(例如,BeginWrite 和 EndWrite以实现异步写入操作)。 不建议新的开发使用此模式。
3.2 TAP(基于任务的异步模式)
C# 拥有语言级别的异步编程模型,它遵循基于任务的异步模式。
C# 异步编程的核心是 Task 和 Task<T> 对象(深入了解Task和Task<T>),这两个对象对异步操作建模。它们受关键字 async 和 await 的支持(二者是异步编程的核心关键字)。通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。 使用 async 关键字定义的异步方法简称为“异步方法”。await 关键字控制执行 await 的方法的调用方,且它最终允许 UI 具有响应性或服务具有灵活性。
4. 异步编程 示例
1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace ConsoleApp 6 { 7 class Program 8 { 9 static async Task Main(string[] args) 10 { 11 try 12 { 13 //线程池,最大和最小数量(CompletionPortThreads 异步I/O线程数量) 14 //相关资料:https://docs.microsoft.com/zh-cn/dotnet/standard/threading/the-managed-thread-pool 15 ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads); //6 6 16 ThreadPool.GetMaxThreads(out int maxWorkerThreads, out int maxCompletionPortThreads); //32767 1000 17 18 ShowMsg(minWorkerThreads.ToString()); 19 ShowMsg(minCompletionPortThreads.ToString()); 20 ShowMsg(maxWorkerThreads.ToString()); 21 ShowMsg(maxCompletionPortThreads.ToString()); 22 23 #region 【1. 使用示例】 24 ////测试async和await(运行在ThreadPool) 25 ////相关资料:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model 26 //ShowMsg($"【Main start 1】"); 27 //Test1Async(); //异步执行,但是不知道什么时候执行 28 //Test11Async(); //异步执行,但是不知道什么时候执行 29 //ShowMsg($"【Main end 1】"); 30 //ShowMsg($"【Main start 2】"); 31 //await Test2Async(); //同步执行,等待任务执行完成 32 //ShowMsg($"【Main end 2】"); 33 34 //ShowMsg($"【Main start 3】"); 35 //Task<String> task = Test3Async(); //有返回值的方法,异步执行,但是不知道什么时候执行 36 //ShowMsg($"【Main end Task】"); 37 //ShowMsg($"{task.Result}"); //等待异步操作执行完成之后,获取执行结果(相当于await) 38 39 //ShowMsg($"【Main end 3】"); 40 41 //TaskRun(); 42 43 //ShowMsg($"【Used samples end】"); 44 45 #endregion 46 47 #region 【2. 错误捕获示例】 48 ////相关资料:https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/try-catch#async-method-example 49 //// sample 1 50 //await Exception1Async(); 51 52 //// sample 2 53 //Task<String> exceptionSample1 = Exception1Async(); 54 //exceptionSample1.Wait(); 55 56 //// sample 3 57 //Console.WriteLine(exceptionSample1.Result); 58 59 //// sample 4 60 //Exception1Async(); 61 62 // 结论: 63 // 以上四种示例,前三种会进入外层try-catch,第四种则不会(因为是异步执行,不需要等待回传结果,需要自行处理报错信息) 64 65 //if (null != exceptionSample1.Exception) 66 //{ 67 //} 68 #endregion 69 70 } 71 catch (Exception ex) 72 { 73 Console.WriteLine(ex); 74 } 75 76 ShowMsg("The End!"); 77 } 78 79 static async Task Test1Async() 80 { 81 ShowMsg($"Test start 1"); 82 await Task.Delay(300); 83 ShowMsg($"Test end 1"); 84 } 85 86 static async Task Test11Async() 87 { 88 ShowMsg($"Test start 1111"); 89 await Task.Delay(300); 90 ShowMsg($"Test end 1111"); 91 } 92 93 static async Task Test2Async() 94 { 95 ShowMsg($"Test start 2"); 96 await Task.Delay(300); 97 ShowMsg($"Test end 2"); 98 } 99 100 static async Task<String> Test3Async() 101 { 102 ShowMsg($"Test start 3"); 103 await Task.Delay(300); 104 ShowMsg($"Test end 3"); 105 return "123"; 106 } 107 108 static void ShowMsg(String msg) 109 { 110 Console.WriteLine($" Time:{ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fffff") } . Message: {msg}"); 111 } 112 113 static async Task<String> Exception1Async() 114 { 115 await Task.Delay(100); 116 117 // Uncomment each of the following lines to 118 // demonstrate exception handling. 119 120 throw new OperationCanceledException("canceled"); 121 throw new Exception("Something happened."); 122 return "Done"; 123 } 124 125 static async Task TaskRun() 126 { 127 await Task.Run(() => 128 { 129 Thread.Sleep(100); 130 ShowMsg("This is task run method."); 131 }); 132 } 133 134 } 135 }
附录
异步编程
https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Concepts
异步编程模式
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/
C# 异步编程
https://docs.microsoft.com/zh-cn/dotnet/csharp/async
使用 Async 和 Await 的异步编程
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/
异步编程模型
https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model
try-catch(C# 参考)
https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/try-catch
托管线程池
https://docs.microsoft.com/zh-cn/dotnet/standard/threading/the-managed-thread-pool
深入了解异步
https://docs.microsoft.com/zh-cn/dotnet/standard/async-in-depth
基于任务的异步模式
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap
任务并行库
https://docs.microsoft.com/zh-cn/dotnet/standard/parallel-programming/task-parallel-library-tpl