0%

Net Core 基础

本篇记录读完第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 可以大大简化代码编写

  • 异步编程可以提高服务器接待请求的数量,但不会使得单个请求的处理效率变高,甚至可能略有降低

  • async 关键字修饰方法后,这个方法就变成异步方法,有几点要注意:

    • 异步方法返回值一般是 Task<T>,其中 T 是真正的返回类型

    • 按照约定,异步方法的名字以 Async 结尾

    • 如果异步方法没有返回值,可以把返回值声明为 void,这样语法上是可以的,但是最好把返回值声明为非泛型的 Task 类型

    • 调用泛型方法的时候,一般在方法前加上 await 关键字,这样方法调用的返回值就是泛型指定的 T 类型的值

    • 一个方法中如果有 await 调用,这个方法也必须用 async 修饰,异步方法具有传染性

    • 只要方法返回值是 TaskTask<T> 类型,就可以使用 await 关键字对其进行调用

    • 修饰为 async 只是为了在方法内使用 await 关键字


异步编程的原理

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 后会看到:

Maind_0

<<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;

// Token: 0x02000003 RID: 3
[CompilerGenerated]
private sealed class <<Main>$>d__0 : IAsyncStateMachine
{
// Token: 0x06000005 RID: 5 RVA: 0x000020CC File Offset: 0x000002CC
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();
}

// Token: 0x06000006 RID: 6 RVA: 0x00002388 File Offset: 0x00000588
[DebuggerHidden]
void IAsyncStateMachine.SetStateMachine([Nullable(1)] IAsyncStateMachine stateMachine)
{
}

// Token: 0x04000001 RID: 1
public int <>1__state;

// Token: 0x04000002 RID: 2
public AsyncTaskMethodBuilder <>t__builder;

// Token: 0x04000003 RID: 3
public string[] args;

// Token: 0x04000004 RID: 4
private HttpClient <httpClient>5__1;

// Token: 0x04000005 RID: 5
private string <html>5__2;

// Token: 0x04000006 RID: 6
private string <destFilePath>5__3;

// Token: 0x04000007 RID: 7
private string <content>5__4;

// Token: 0x04000008 RID: 8
private string <content2>5__5;

// Token: 0x04000009 RID: 9
private string <>s__6;

// Token: 0x0400000A RID: 10
private string <>s__7;

// Token: 0x0400000B RID: 11
[Nullable(new byte[]
{
0,
1
})]
private TaskAwaiter<string> <>u__1;

// Token: 0x0400000C RID: 12
private TaskAwaiter <>u__2;
}

这是一个典型的状态机模式,<>1__state 记录当前执行到哪个状态,MoveNext 方法会被多次调用,每次被调用时都表明对象进入了下一个状态

反编译 Main 方法:

Main_Method

可以看到 Main 方法中创建了一个 <<Main>$>d__0 类的对象,并调用 AsyncTaskMethodBuilder 去执行 <<Main>$>d__0 类的状态机,从而完成异步调用

总结下 async 方法的调用分成 3 步:

  1. 方法会被 C# 编译器编译成一个类

  2. 根据 await 调用把方法切分为多个状态

  3. async 方法的调用拆分为若干次对 MoveNext 方法的调用


async 方法背后的线程切换

异步的真正价值:在对异步方法进行 await 调用的等待时间,框架会把当前的线程返回给线程池,等异步方法调用执行完毕后,框架会从线程池中再取出一个线程执行后续的代码

这种由不用线程执行不同代码段的行为称为 线程切换

执行一段代码:

thread_switch

该输出结果表明:异步调用前的线程在异步等待期间会被放回线程池,异步方法执行完毕后,一个新的空闲线程从线程池中取出来,以执行后续的代码。(也许会是同一个线程)

为什么异步方法中的代码能运行在不同线程上?

因为 async 方法被拆分成了多次对 MoveNext 的调用,多次当然可以运行在不同的线程

因而,异步编程的好处就是:每个线程都不会空等某个操作,服务器处理并发请求的能力就提升了。

await async 只是让开发像编写同步代码一样编写异步代码,只是看起来线程在等待异步方法的执行,实际并没有


异步方法 != 多线程