C#之async和await原理探究

C#的异步编程强大而优雅,但其实现原理也异常复杂。本文以一个简单实例作为切入点,一窥底层技术的奥秘,也体验驾驭复杂代码逻辑的乐趣。

C# 实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal class Program {
public static async Task Main() {
Console.WriteLine($"线程号a:{Thread.CurrentThread.ManagedThreadId}");
Console.WriteLine(await GetPageLengthAsync("https://www.baidu.com"));
Console.ReadKey();
}

static async Task<int> GetPageLengthAsync(string url) {
Console.WriteLine($"线程号b:{Thread.CurrentThread.ManagedThreadId}");

using (HttpClient client = new HttpClient()) {
var str = await client.GetStringAsync(url);
Console.WriteLine($"线程号c:{Thread.CurrentThread.ManagedThreadId}");
return str.Length;
}
}
}

反编译代码

1
2
3
4
private static void <Main>()
{
Program.Main().GetAwaiter().GetResult();
}
1
2
3
4
5
6
7
8
public static Task Main()
{
Program.<Main>d__0 <Main>d__ = new Program.<Main>d__0();
<Main>d__.<>t__builder = AsyncTaskMethodBuilder.Create();
<Main>d__.<>1__state = -1;
<Main>d__.<>t__builder.Start<Program.<Main>d__0>(ref <Main>d__);
return <Main>d__.<>t__builder.Task;
}
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
private sealed class <Main>d__0 : IAsyncStateMachine
{
public <Main>d__0() {}
void IAsyncStateMachine.MoveNext()
{
int num = this.<>1__state;
try
{
TaskAwaiter<int> awaiter;
if (num != 0)
{
Console.WriteLine(string.Format("线程号a:{0}", Thread.CurrentThread.ManagedThreadId));
awaiter = Program.GetPageLengthAsync("https://www.baidu.com").GetAwaiter();
if (!awaiter.IsCompleted)
{
this.<>1__state = 0;
this.<>u__1 = awaiter;
Program.<Main>d__0 <Main>d__ = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<int>, Program.<Main>d__0>(ref awaiter, ref <Main>d__);
return;
}
}
else
{
awaiter = this.<>u__1;
this.<>u__1 = default(TaskAwaiter<int>);
this.<>1__state = -1;
}
this.<>s__1 = awaiter.GetResult();
Console.WriteLine(this.<>s__1);
Console.ReadKey();
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult();
}

void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) {}

public int <>1__state;
public AsyncTaskMethodBuilder <>t__builder;
private int <>s__1;
private TaskAwaiter<int> <>u__1;
}
1
2
3
4
5
6
7
8
9
private static Task<int> GetPageLengthAsync(string url)
{
Program.<GetPageLengthAsync>d__1 <GetPageLengthAsync>d__ = new Program.<GetPageLengthAsync>d__1();
<GetPageLengthAsync>d__.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
<GetPageLengthAsync>d__.url = url;
<GetPageLengthAsync>d__.<>1__state = -1;
<GetPageLengthAsync>d__.<>t__builder.Start<Program.<GetPageLengthAsync>d__1>(ref <GetPageLengthAsync>d__);
return <GetPageLengthAsync>d__.<>t__builder.Task;
}
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
private sealed class <GetPageLengthAsync>d__1 : IAsyncStateMachine
{
public <GetPageLengthAsync>d__1() {}
void IAsyncStateMachine.MoveNext()
{
int num = this.<>1__state;
int length;
try
{
if (num != 0)
{
Console.WriteLine(string.Format("线程号b:{0}", Thread.CurrentThread.ManagedThreadId));
this.<client>5__1 = new HttpClient();
}
try
{
TaskAwaiter<string> awaiter;
if (num != 0)
{
awaiter = this.<client>5__1.GetStringAsync(this.url).GetAwaiter();
if (!awaiter.IsCompleted)
{
num = (this.<>1__state = 0);
this.<>u__1 = awaiter;
Program.<GetPageLengthAsync>d__1 <GetPageLengthAsync>d__ = this;
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, Program.<GetPageLengthAsync>d__1>(ref awaiter, ref <GetPageLengthAsync>d__);
return;
}
}
else
{
awaiter = this.<>u__1;
this.<>u__1 = default(TaskAwaiter<string>);
num = (this.<>1__state = -1);
}
this.<>s__3 = awaiter.GetResult();
this.<str>5__2 = this.<>s__3;
this.<>s__3 = null;
Console.WriteLine(string.Format("线程号c:{0}", Thread.CurrentThread.ManagedThreadId));
length = this.<str>5__2.Length;
}
finally
{
if (num < 0 && this.<client>5__1 != null)
{
((IDisposable)this.<client>5__1).Dispose();
}
}
}
catch (Exception exception)
{
this.<>1__state = -2;
this.<>t__builder.SetException(exception);
return;
}
this.<>1__state = -2;
this.<>t__builder.SetResult(length);
}

void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) {}

public int <>1__state;
public AsyncTaskMethodBuilder<int> <>t__builder;
public string url;
private HttpClient <client>5__1;
private string <str>5__2;
private string <>s__3;
private TaskAwaiter<string> <>u__1;
}

代码执行流程

  • 凡事必有源头,框架提供的原生异步函数(本文称之为“元异步函数”)是基于系统API实现的,只有用户定义的异步函数才会被转成状态机。比如这里的awaiter = this.<client>5__2.GetStringAsync(this.url).GetAwaiter(),其中GetStringAsync()就是元异步函数, 不要用状态机的思维去理解它,只需要明白这行代码执行后,GetStringAsync()已经在后台执行了,并且不会阻塞当前线程。
  • 元异步函数基于系统底层API实现。
  • 如果一个MoveNext()函数中的异步操作没有结束,MoveNext()会把当前MoveNext()函数注册为当前waiter的回调函数,更新状态机状态后直接return(这就明白了为什么await异步函数不会阻塞当前线程)。待这个异步操作完成时,会自动触发回调,重新进入MoveNext()的逻辑中,根据更新后的状态机变量执行对应的分支逻辑。
  • 当元异步函数执行完成时,调用链上的状态机就会像多米诺骨牌一样依次进入回调函数逻辑。

不使用await关键字

如果在Main函数中调用GetPageLengthAsync()异步函数时不使用await关键字,那么会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
internal class Program {
public static void Main() {
Console.WriteLine($"线程号a:{Thread.CurrentThread.ManagedThreadId}");
var task = GetPageLengthAsync("https://www.baidu.com");
Console.ReadKey();
}

static async Task<int> GetPageLengthAsync(string url) {
Console.WriteLine($"线程号b:{Thread.CurrentThread.ManagedThreadId}");

using (HttpClient client = new HttpClient()) {
var str = await client.GetStringAsync(url);
Console.WriteLine($"线程号c:{Thread.CurrentThread.ManagedThreadId}");
return str.Length;
}
}
}

这时的分析就非常简单了,GetPageLengthAsync()对应的状态机未获得结果第一次返回后,Main函数的逻辑就继续向下了,没有等待该异步函数最终结果的机制。

(转载本站文章请注明作者和出处lihaohello.top,请勿用于任何商业用途)

评论