async和await原理

1
2
3
4
5
6
7
8
9
10
11
using (HttpClient httpClient = new())
{
string html = await httpClient.GetStringAsync("https://www.baidu.com");
Console.WriteLine(html);
}
string txt = "async";
string filename = "async.txt";
await File.WriteAllTextAsync(filename, txt);
Console.WriteLine("写入成功");
string s = await File.ReadAllTextAsync(filename);
Console.WriteLine($"文件内容:{s}");

有上述一段代码,将其反编译。

生成两个Main函数。

程序入口为void Main

1
2
3
4
5
6
7
8
9
using System.Diagnostics;
using System.Runtime.CompilerServices;

[SpecialName]
[DebuggerStepThrough]
private static void <Main>(string[] args)
{
<Main>$(args).GetAwaiter().GetResult();
}

void Main调用Task <Main>$Task <Main>$中新建类<<Main>>d__0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;

[AsyncStateMachine(typeof(<<Main>$>d__0))]
[DebuggerStepThrough]
private static Task <Main>$(string[] args)
{
<<Main>$>d__0 stateMachine = new <<Main>$>d__0();
stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
stateMachine.args = args;
stateMachine.<>1__state = -1;
stateMachine.<>t__builder.Start(ref stateMachine);
return stateMachine.<>t__builder.Task;
}

async函数被编译器写成一个类,在MoveNext函数中根据await被切分为多个状态,MoveNext被调用多次,使用状态机模型完成。

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
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Runtime.CompilerServices;

[CompilerGenerated]
private sealed class <<Main>$>d__0 : IAsyncStateMachine
{
public int <>1__state;

public AsyncTaskMethodBuilder <>t__builder;

public string[] args;

private string <txt>5__1;

private string <filename>5__2;

private string <s>5__3;

private HttpClient <httpClient>5__4;

private string <html>5__5;

private string <>s__6;

private string <>s__7;

private TaskAwaiter<string> <>u__1;

private TaskAwaiter <>u__2;

private void MoveNext()
{
int num = <>1__state;
try
{
TaskAwaiter awaiter2;
TaskAwaiter<string> awaiter;
switch (num)
{
default:
<httpClient>5__4 = new HttpClient();
goto case 0;
case 0:
try
{
TaskAwaiter<string> awaiter3;
if (num != 0)
{
awaiter3 = <httpClient>5__4.GetStringAsync("https://www.baidu.com").GetAwaiter();
if (!awaiter3.IsCompleted)
{
num = (<>1__state = 0);
<>u__1 = awaiter3;
<<Main>$>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
return;
}
}
else
{
awaiter3 = <>u__1;
<>u__1 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
}
<>s__6 = awaiter3.GetResult();
<html>5__5 = <>s__6;
<>s__6 = null;
Console.WriteLine(<html>5__5);
<html>5__5 = null;
}
finally
{
if (num < 0 && <httpClient>5__4 != null)
{
((IDisposable)<httpClient>5__4).Dispose();
}
}
<httpClient>5__4 = null;
<txt>5__1 = "async";
<filename>5__2 = "async.txt";
awaiter2 = File.WriteAllTextAsync(<filename>5__2, <txt>5__1).GetAwaiter();
if (!awaiter2.IsCompleted)
{
num = (<>1__state = 1);
<>u__2 = awaiter2;
<<Main>$>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
return;
}
goto IL_0176;
case 1:
awaiter2 = <>u__2;
<>u__2 = default(TaskAwaiter);
num = (<>1__state = -1);
goto IL_0176;
case 2:
{
awaiter = <>u__1;
<>u__1 = default(TaskAwaiter<string>);
num = (<>1__state = -1);
break;
}
IL_0176:
awaiter2.GetResult();
Console.WriteLine("写入成功");
awaiter = File.ReadAllTextAsync(<filename>5__2).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (<>1__state = 2);
<>u__1 = awaiter;
<<Main>$>d__0 stateMachine = this;
<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
return;
}
break;
}
<>s__7 = awaiter.GetResult();
<s>5__3 = <>s__7;
<>s__7 = null;
Console.WriteLine("文件内容:" + <s>5__3);
}
catch (Exception exception)
{
<>1__state = -2;
<txt>5__1 = null;
<filename>5__2 = null;
<s>5__3 = null;
<>t__builder.SetException(exception);
return;
}
<>1__state = -2;
<txt>5__1 = null;
<filename>5__2 = null;
<s>5__3 = null;
<>t__builder.SetResult();
}

void IAsyncStateMachine.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
this.MoveNext();
}

[DebuggerHidden]
private void SetStateMachine(IAsyncStateMachine stateMachine)
{
}

void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
{
//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
this.SetStateMachine(stateMachine);
}
}

async的线程切换

await调用的等待期间,.NET会把当前的线程返回给线程池,等async方法调用执行完后,.NET会从线程池中再取出一个线程执行后续的代码。

1
2
3
4
5
6
7
8
9
10
using System.Text;

Console.WriteLine(Environment.CurrentManagedThreadId);
StringBuilder stringBuilder = new();
for (int i = 0; i < 10000; i++)
{
stringBuilder.Append("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
}
await File.WriteAllTextAsync("async.txt", stringBuilder.ToString());
Console.WriteLine(Environment.CurrentManagedThreadId);

上面代码两次输出的线程ID大概率是不一样的。

异步方法的代码并不会自动在新线程中执行,除非把代码放到新线程中执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static async Task<double> CalcAsync(int n)
{
Console.WriteLine($"CalcAsync线程:{Environment.CurrentManagedThreadId}");
double result = 0;
Random random = new();
for (int i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;
}

Console.WriteLine($"调用之前线程:{Environment.CurrentManagedThreadId}");
double result = await CalcAsync(500);
Console.WriteLine($"result = {result}");
Console.WriteLine($"调用之后线程:{Environment.CurrentManagedThreadId}");

上面的代码会产生警告,打印出的线程都是相同的。

CalcAsync缺少await运算符,将以同步方式进行。

要用异步方式执行,一种方法是用Task.Run,这样打印出来的线程是不一样的。上一例中调用File.WriteAllTextAsync是异步的,我们将其反编译后,可以发现其间接引用了一个包含Task.Run的函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static async Task<double> CalcAsync(int n)
{
return await Task.Run(() =>
{
Console.WriteLine($"CalcAsync线程:{Environment.CurrentManagedThreadId}");
double result = 0;
Random random = new();
for (int i = 0; i < n * n; i++)
{
result += random.NextDouble();
}
return result;
});
}

Console.WriteLine($"调用之前线程:{Environment.CurrentManagedThreadId}");
double result = await CalcAsync(500);
Console.WriteLine($"result = {result}");
Console.WriteLine($"调用之后线程:{Environment.CurrentManagedThreadId}");