C# 中的异步编程
一,编写的目的
主要是为了介绍C#中异步编程相关的概念与本人使用的心得总结,对阅读与参考的网络资料进行了整理与归类,记录与明确本人在日常代码中关于异步编程的一些编写习惯与规范。
二、预期读者
熟悉.NET编程,具有.NET代码的阅读与编写基础。
三、异步编程的一些基本概念
-
异步编程模式| Microsoft Docs
-
线程Thread类| Microsoft Docs
-
线程池ThreadPool类| Microsoft Docs
-
异步任务Task类| Microsoft Docs
-
任务计划TaskScheduler类| Microsoft Docs
-
async修饰符| Microsoft Docs
-
await运算符| Microsoft Docs
-
Task.ConfigureAwait方法| Microsoft Docs
ConfigureAwait FAQ 英文版
ConfigureAwait FAQ 中文版
Task.ConfigureAwait方法能来做什么
使用 Task.Wait()?立刻死锁
-
异步编程| Microsoft Docs
-
Async/Await 异步编程中的最佳做法| Microsoft Docs
四、C# 中的异步编程例子
-
Task的使用
1、C# Task详解
2、C# Task和async/await详解
3、Task调度器
4、优雅地使用C#异步
5、C#同步方法中如何调用异步方法
-
Task.Run和Task.Factory.StartNew
1、C# Task.Run 和 Task.Factory.StartNew 区别
2、Task.Run Vs Task.Factory.StartNew
-
使用 Async 和 Await 的异步编程
1、async和await详解
2、核心代码
1 public class Breakfast 2 { 3 /// <summary> 4 /// 同步版本 5 /// </summary> 6 [Benchmark(Baseline = true)] 7 public void RunV1() 8 { 9 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 10 var st = Stopwatch.StartNew(); 11 12 Coffee cup = PourCoffee(); 13 PourCoffeeSuccess(); 14 15 Egg eggs = FryEggs(2); 16 FryEggsSuccess(); 17 18 Bacon bacon = FryBacon(3); 19 FryBaconSuccess(); 20 21 Toast toast = ToastBread(2); 22 ApplyButter(toast); 23 ApplyJam(toast); 24 ToastBreadSuccess(); 25 26 Juice oj = PourOJ(); 27 PourOJSuccess(); 28 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 29 st.Stop(); 30 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 31 } 32 33 /// <summary> 34 /// 异步版本,注意 await 关键字 35 /// </summary> 36 /// <returns></returns> 37 [Benchmark] 38 public async Task RunV2() 39 { 40 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 41 var st = Stopwatch.StartNew(); 42 43 Coffee cup = PourCoffee(); 44 PourCoffeeSuccess(); 45 46 // 异步煎鸡蛋 47 Egg eggs = await FryEggsAsync(2); 48 FryEggsSuccess(); 49 50 // 异步煎培根 51 Bacon bacon = await FryBaconAsync(3); 52 FryBaconSuccess(); 53 54 // 异步烤面包 55 Toast toast = await ToastBreadAsync(2); 56 ApplyButter(toast); 57 ApplyJam(toast); 58 ToastBreadSuccess(); 59 60 Juice oj = PourOJ(); 61 PourOJSuccess(); 62 63 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 64 st.Stop(); 65 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 66 } 67 68 [Benchmark] 69 public async Task RunV3() 70 { 71 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 72 var st = Stopwatch.StartNew(); 73 74 Coffee cup = PourCoffee(); 75 PourCoffeeSuccess(); 76 77 var eggsTask = FryEggsAsync(2); 78 await eggsTask; 79 FryEggsSuccess(); 80 81 var baconTask = FryBaconAsync(3); 82 await baconTask; 83 FryBaconSuccess(); 84 85 var toastTask = ToastBreadAsync(2); 86 var toast = await toastTask; 87 ApplyButter(toast); 88 ApplyJam(toast); 89 ToastBreadSuccess(); 90 91 Juice oj = PourOJ(); 92 PourOJSuccess(); 93 94 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 95 st.Stop(); 96 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 97 } 98 99 [Benchmark] 100 public async Task RunV4() 101 { 102 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 103 var st = Stopwatch.StartNew(); 104 105 Coffee cup = PourCoffee(); 106 PourCoffeeSuccess(); 107 108 var eggsTask = FryEggsAsync(2); 109 var baconTask = FryBaconAsync(3); 110 var toastTask = ToastBreadAsync(2); 111 112 await eggsTask; 113 FryEggsSuccess(); 114 115 await baconTask; 116 FryBaconSuccess(); 117 118 var toast = await toastTask; 119 ApplyButter(toast); 120 ApplyJam(toast); 121 ToastBreadSuccess(); 122 123 Juice oj = PourOJ(); 124 PourOJSuccess(); 125 126 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 127 st.Stop(); 128 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 129 } 130 131 [Benchmark] 132 public async Task RunV5() 133 { 134 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 135 var st = Stopwatch.StartNew(); 136 137 Coffee cup = PourCoffee(); 138 PourCoffeeSuccess(); 139 140 var eggsTask = FryEggsAsync(2); 141 var baconTask = FryBaconAsync(3); 142 var toastTask = MakeToastWithButterAndJamAsync(2); 143 144 await eggsTask; 145 FryEggsSuccess(); 146 147 await baconTask; 148 FryBaconSuccess(); 149 150 var toast = await toastTask; 151 ToastBreadSuccess(); 152 153 Juice oj = PourOJ(); 154 PourOJSuccess(); 155 156 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 157 st.Stop(); 158 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 159 } 160 161 [Benchmark] 162 public async Task RunV6() 163 { 164 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 165 var st = Stopwatch.StartNew(); 166 167 Coffee cup = PourCoffee(); 168 PourCoffeeSuccess(); 169 170 var eggsTask = FryEggsAsync(2); 171 var baconTask = FryBaconAsync(3); 172 var toastTask = MakeToastWithButterAndJamAsync(2); 173 174 await Task.WhenAll(eggsTask, baconTask, toastTask); 175 FryEggsSuccess(); 176 FryBaconSuccess(); 177 ToastBreadSuccess(); 178 179 Juice oj = PourOJ(); 180 PourOJSuccess(); 181 182 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 183 st.Stop(); 184 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 185 } 186 187 [Benchmark] 188 public async Task RunV7() 189 { 190 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 开始...."); 191 var st = Stopwatch.StartNew(); 192 193 Coffee cup = PourCoffee(); 194 PourCoffeeSuccess(); 195 196 var eggsTask = FryEggsAsync(2); 197 var baconTask = FryBaconAsync(3); 198 var toastTask = MakeToastWithButterAndJamAsync(2); 199 var breakfastTasks = new List<Task>() { eggsTask, baconTask, toastTask }; 200 while (breakfastTasks.Count > 0) 201 { 202 var task = await Task.WhenAny(breakfastTasks); 203 if (task == eggsTask) 204 { 205 FryEggsSuccess(); 206 } 207 else if (task == baconTask) 208 { 209 FryBaconSuccess(); 210 } 211 else if (task == toastTask) 212 { 213 ToastBreadSuccess(); 214 } 215 216 breakfastTasks.Remove(task); 217 } 218 219 Juice oj = PourOJ(); 220 PourOJSuccess(); 221 222 Console.WriteLine($"Breakfast is ready!{Environment.NewLine}"); 223 st.Stop(); 224 Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} 结束,耗时 = {st.ElapsedMilliseconds.ToString("###,###")} 毫秒"); 225 } 226 227 228 /// <summary> 229 /// 倒一杯橙汁 230 /// </summary> 231 /// <returns></returns> 232 private static Juice PourOJ() 233 { 234 Console.WriteLine("Pouring orange juice(倒一杯橙汁)"); 235 Thread.Sleep(100); 236 return new Juice(); 237 } 238 /// <summary> 239 /// 面包加果酱 240 /// </summary> 241 /// <param name="toast"></param> 242 private static void ApplyJam(Toast toast) 243 { 244 Thread.Sleep(50); 245 Console.WriteLine("Putting jam on the toast(面包加果酱)"); 246 } 247 /// <summary> 248 /// 面包加黄油 249 /// </summary> 250 /// <param name="toast"></param> 251 private static void ApplyButter(Toast toast) 252 { 253 Thread.Sleep(50); 254 Console.WriteLine("Putting butter on the toast(面包加黄油)"); 255 } 256 /// <summary> 257 /// 烤面包 258 /// </summary> 259 /// <param name="slices"></param> 260 /// <returns></returns> 261 private static Toast ToastBread(int slices) 262 { 263 Console.WriteLine("Start toasting(烤面包)..."); 264 for (int slice = 0; slice < slices; slice++) 265 { 266 Console.WriteLine("Putting a slice of bread in the toaster"); 267 Thread.Sleep(100); 268 } 269 Console.WriteLine("Remove toast from toaster"); 270 271 return new Toast(); 272 } 273 /// <summary> 274 /// 煎培根 275 /// </summary> 276 /// <param name="slices"></param> 277 /// <returns></returns> 278 private static Bacon FryBacon(int slices) 279 { 280 Console.WriteLine($"putting {slices} slices of bacon in the pan(煎培根)"); 281 Console.WriteLine("cooking first side of bacon..."); 282 for (int slice = 0; slice < slices; slice++) 283 { 284 Console.WriteLine("flipping a slice of bacon"); 285 Thread.Sleep(100); 286 } 287 Console.WriteLine("cooking the second side of bacon..."); 288 Console.WriteLine("Put bacon on plate"); 289 290 return new Bacon(); 291 } 292 /// <summary> 293 /// 煎鸡蛋 294 /// </summary> 295 /// <param name="howMany"></param> 296 /// <returns></returns> 297 private static Egg FryEggs(int howMany) 298 { 299 Console.WriteLine("Warming the egg pan(煎鸡蛋)..."); 300 Console.WriteLine($"cracking {howMany} eggs"); 301 Console.WriteLine("cooking the eggs ..."); 302 for (int i = 0; i < howMany; i++) 303 { 304 Thread.Sleep(100); 305 } 306 Console.WriteLine("Put eggs on plate"); 307 308 return new Egg(); 309 } 310 /// <summary> 311 /// 倒咖啡 312 /// </summary> 313 /// <returns></returns> 314 private static Coffee PourCoffee() 315 { 316 Console.WriteLine("Pouring coffee(倒咖啡)"); 317 Thread.Sleep(100); 318 return new Coffee(); 319 } 320 321 private static void PourOJSuccess() 322 { 323 Console.WriteLine("oj is ready(橙汁已倒好)"); 324 } 325 private static void ToastBreadSuccess() 326 { 327 Console.WriteLine($"toast is ready(面包已烤好){Environment.NewLine}"); 328 } 329 private static void FryBaconSuccess() 330 { 331 Console.WriteLine($"bacon is ready({3}片培根已煎好){Environment.NewLine}"); 332 } 333 private static void FryEggsSuccess() 334 { 335 Console.WriteLine($"eggs are ready({2}个鸡蛋已煎好){Environment.NewLine}"); 336 } 337 private static void PourCoffeeSuccess() 338 { 339 Console.WriteLine($"coffee is ready(咖啡已倒好){Environment.NewLine}"); 340 } 341 342 343 /// <summary> 344 /// 煎鸡蛋 345 /// </summary> 346 /// <param name="howMany"></param> 347 /// <returns></returns> 348 private static async Task<Egg> FryEggsAsync(int howMany) 349 { 350 Console.WriteLine("Warming the egg pan(煎鸡蛋)..."); 351 Console.WriteLine($"cracking {howMany} eggs"); 352 Console.WriteLine("cooking the eggs ..."); 353 for (int i = 0; i < howMany; i++) 354 { 355 await Task.Delay(100); 356 } 357 Console.WriteLine("Put eggs on plate"); 358 359 return new Egg(); 360 } 361 /// <summary> 362 /// 煎培根 363 /// </summary> 364 /// <param name="slices"></param> 365 /// <returns></returns> 366 private static async Task<Bacon> FryBaconAsync(int slices) 367 { 368 Console.WriteLine($"putting {slices} slices of bacon in the pan(煎培根)"); 369 Console.WriteLine("cooking first side of bacon..."); 370 for (int slice = 0; slice < slices; slice++) 371 { 372 Console.WriteLine("flipping a slice of bacon"); 373 await Task.Delay(100); 374 } 375 Console.WriteLine("cooking the second side of bacon..."); 376 Console.WriteLine("Put bacon on plate"); 377 378 return new Bacon(); 379 } 380 /// <summary> 381 /// 烤面包 382 /// </summary> 383 /// <param name="slices"></param> 384 /// <returns></returns> 385 private static async Task<Toast> ToastBreadAsync(int slices) 386 { 387 Console.WriteLine("Start toasting(烤面包)..."); 388 for (int slice = 0; slice < slices; slice++) 389 { 390 Console.WriteLine("Putting a slice of bread in the toaster"); 391 await Task.Delay(100); 392 } 393 //Console.WriteLine("Fire! Toast is ruined!"); 394 //throw new InvalidOperationException("The toaster is on fire"); 395 Console.WriteLine("Remove toast from toaster"); 396 397 return new Toast(); 398 } 399 /// <summary> 400 /// 烤面包 + 黄油 + 果酱 401 /// </summary> 402 /// <param name="number"></param> 403 /// <returns></returns> 404 private static async Task<Toast> MakeToastWithButterAndJamAsync(int number) 405 { 406 var toast = await ToastBreadAsync(number); 407 ApplyButter(toast); 408 ApplyJam(toast); 409 410 return toast; 411 } 412 }
代码下载,点击此处
五、工作机制与原理
六、总结
-
推荐做法
-
在.NET中,推荐使用基于任务的异步模式。它基于 System.Threading.Tasks 命名空间中的 Task 和 Task<TResult> 类型,这些类型用于表示异步操作。
-
避免 Async Void 最好使用 async Task 方法而不是 async void 方法。
-
始终使用 Async 不要混合阻塞式代码和异步代码。
-
配置上下文尽可能使用 ConfigureAwait(false)。
Async/Await 异步编程中的最佳做法 | Microsoft Docs
-
使用过程中可能出现的误区
-
Task.Factory.StartNew(async () =>{});的写法
如下面的错误写法:
var task= Task.Factory.StartNew(async () => { // task job. await Task.Delay(1000); }); await task;
正确写法
方式一(推荐):
var task= Task.Run(async () => { // task job. await Task.Delay(1000); }); await task;
方式二:
var task= Task.Factory.StartNew(async () => { // task job. await Task.Delay(1000); }).Unwrap(); await task;
方式三:
var task= await Task.Factory.StartNew(async () => { // task job. await Task.Delay(1000); }); await task;
相关文章:Task.Run(), Task.Factory.StartNew() 和 New Task() 的行为不一致分析
七、特别声明
-
本文引用了其他博友写的文章,如果有涉及到侵权问题请及时联系我进行删除。
-
本文涉及到的一些文章未经过严格的推敲于论证,如果大家有发现问题欢迎大家指出,然后我再进行更改。
-
涉及到这个模块好的文章也欢迎大家进行推荐或自荐,本人阅读论证后会进行引用添加。