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() { this .MoveNext(); } [DebuggerHidden ] private void SetStateMachine (IAsyncStateMachine stateMachine ) { } void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { 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} " );