本篇记录读完第1和2章内容的一些收获。
.NET Core 不是 .NET Framework 的升级版,而是一个从头开始开发的全新平台,因此在 .NET Framework 下开发的程序不能直接在 .NET Core 下运行
.NET Core 的很多代码都是直接从 .NET Framework 中迁移或改造过来的,这就意味着绝大多数类的用法没变
AppDomain 不再被 .NET Core 支持,在 .NET Framework 中,AppDomain 可以用来在进程内对代码执行进行隔离,但其本身有很多缺陷和局限,.NET Core 不再支持
.NET Standard 是一个规范,只是规定了需要被实现的规范,但不负责具体实现
.NET Standard 定义了 .NET Core、.NET Framework、Xamarin 的交集,只要是 .NET Standard 类库,都可以被 .NET Core、.NET Framework 和 Xamarin 等项目引用
C# 9.0 中新增 Record 类型,编译器会自动生成 Equals、GetHashcode 等方法,是一个语法糖,用于简化一种pattern的类编写
在需要编写不可变类并且需要进行对象值比较时,使用 Record 可以大大简化代码编写
异步编程的原理 1 2 3 4 5 6 7 8 9 using var httpClient = new HttpClient();var html = await httpClient.GetStringAsync("https://www.ptpress.com.cn" );Console.WriteLine(html); var destFilePath = "C:/1.txt" ;var content = "hello async and await" ;await File.WriteAllTextAsync(destFilePath, content);var content2 = await File.ReadAllTextAsync(destFilePath);Console.WriteLine(content2);
使用 dnSpy 打开其编译的 dll 后会看到:
<<Main>$>d__0 类实现了状态机的 IAsyncStateMachine
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 using System;using System.Diagnostics;using System.IO;using System.Net.Http;using System.Runtime.CompilerServices;using System.Threading;[CompilerGenerated ] private sealed class <<Main >$>d__0 : IAsyncStateMachine { void IAsyncStateMachine.MoveNext() { int num = this .<>1 __state; try { if (num > 2 ) { this .<httpClient>5 __1 = new HttpClient(); } try { TaskAwaiter<string > awaiter; TaskAwaiter awaiter2; TaskAwaiter<string > awaiter3; switch (num) { case 0 : awaiter = this .<>u__1; this .<>u__1 = default (TaskAwaiter<string >); num = (this .<>1 __state = -1 ); break ; case 1 : awaiter2 = this .<>u__2; this .<>u__2 = default (TaskAwaiter); num = (this .<>1 __state = -1 ); goto IL_14C; case 2 : awaiter3 = this .<>u__1; this .<>u__1 = default (TaskAwaiter<string >); num = (this .<>1 __state = -1 ); goto IL_1BE; default : awaiter = this .<httpClient>5 __1.GetStringAsync("https://www.ptpress.com.cn" ).GetAwaiter(); if (!awaiter.IsCompleted) { num = (this .<>1 __state = 0 ); this .<>u__1 = awaiter; Program.<<Main>$>d__0 <<Main>$>d__ = this ; this .<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string >, Program.<<Main>$>d__0>(ref awaiter, ref <<Main>$>d__); return ; } break ; } this .<>s__6 = awaiter.GetResult(); this .<html>5 __2 = this .<>s__6; this .<>s__6 = null ; Console.WriteLine(this .<html>5 __2); this .<destFilePath>5 __3 = "C:/1.txt" ; this .<content>5 __4 = "hello async and await" ; awaiter2 = File.WriteAllTextAsync(this .<destFilePath>5 __3, this .<content>5 __4, default (CancellationToken)).GetAwaiter(); if (!awaiter2.IsCompleted) { num = (this .<>1 __state = 1 ); this .<>u__2 = awaiter2; Program.<<Main>$>d__0 <<Main>$>d__ = this ; this .<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, Program.<<Main>$>d__0>(ref awaiter2, ref <<Main>$>d__); return ; } IL_14C: awaiter2.GetResult(); awaiter3 = File.ReadAllTextAsync(this .<destFilePath>5 __3, default (CancellationToken)).GetAwaiter(); if (!awaiter3.IsCompleted) { num = (this .<>1 __state = 2 ); this .<>u__1 = awaiter3; Program.<<Main>$>d__0 <<Main>$>d__ = this ; this .<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string >, Program.<<Main>$>d__0>(ref awaiter3, ref <<Main>$>d__); return ; } IL_1BE: this .<>s__7 = awaiter3.GetResult(); this .<content2>5 __5 = this .<>s__7; this .<>s__7 = null ; Console.WriteLine(this .<content2>5 __5); } finally { if (num < 0 && this .<httpClient>5 __1 != null ) { ((IDisposable)this .<httpClient>5 __1).Dispose(); } } } catch (Exception exception) { this .<>1 __state = -2 ; this .<httpClient>5 __1 = null ; this .<html>5 __2 = null ; this .<destFilePath>5 __3 = null ; this .<content>5 __4 = null ; this .<content2>5 __5 = null ; this .<>t__builder.SetException(exception); return ; } this .<>1 __state = -2 ; this .<httpClient>5 __1 = null ; this .<html>5 __2 = null ; this .<destFilePath>5 __3 = null ; this .<content>5 __4 = null ; this .<content2>5 __5 = null ; this .<>t__builder.SetResult(); } [DebuggerHidden ] void IAsyncStateMachine.SetStateMachine([Nullable(1 )] IAsyncStateMachine stateMachine) { } public int <>1 __state; public AsyncTaskMethodBuilder <>t__builder; public string [] args ; private HttpClient <httpClient>5 __1; private string <html>5 __2; private string <destFilePath>5 __3; private string <content>5 __4; private string <content2>5 __5; private string <>s__6; private string <>s__7; [Nullable(new byte[ ] { 0 , 1 })] private TaskAwaiter<string > <>u__1; private TaskAwaiter <>u__2; }
这是一个典型的状态机模式,<>1__state 记录当前执行到哪个状态,MoveNext 方法会被多次调用,每次被调用时都表明对象进入了下一个状态
反编译 Main 方法:
可以看到 Main 方法中创建了一个 <<Main>$>d__0 类的对象,并调用 AsyncTaskMethodBuilder 去执行 <<Main>$>d__0 类的状态机,从而完成异步调用
总结下 async 方法的调用分成 3 步:
方法会被 C# 编译器编译成一个类
根据 await 调用把方法切分为多个状态
对 async 方法的调用拆分为若干次对 MoveNext 方法的调用
async 方法背后的线程切换 异步的真正价值:在对异步方法进行 await 调用的等待时间,框架会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池中再取出一个线程执行后续的代码
这种由不用线程执行不同代码段的行为称为 线程切换
执行一段代码:
该输出结果表明:异步调用前的线程在异步等待期间会被放回线程池,异步方法执行完毕后,一个新的空闲线程从线程池中取出来,以执行后续的代码。(也许会是同一个线程)
为什么异步方法中的代码能运行在不同线程上?
因为 async 方法被拆分成了多次对 MoveNext 的调用,多次当然可以运行在不同的线程
因而,异步编程的好处就是:每个线程都不会空等某个操作,服务器处理并发请求的能力就提升了。
await async 只是让开发像编写同步代码一样编写异步代码,只是看起来线程在等待异步方法的执行,实际并没有
异步方法 != 多线程