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函数的逻辑就继续向下了,没有等待该异步函数最终结果的机制。