专业手机网站建设价格,网站广告图片在线制作,东营人力资源招聘信息网,做网站选择什么服务器1. 简介 从4.0版本开始.NET引入并行编程库#xff0c;用户能够通过这个库快捷的开发并行计算和并行任务处理的程序。在4.5版本中.NET又引入了Async和Await两个新的关键字#xff0c;在语言层面对并行编程给予进一步的支持#xff0c;使得用户能以一种简洁直观的方式实现并行… 1. 简介 从4.0版本开始.NET引入并行编程库用户能够通过这个库快捷的开发并行计算和并行任务处理的程序。在4.5版本中.NET又引入了Async和Await两个新的关键字在语言层面对并行编程给予进一步的支持使得用户能以一种简洁直观的方式实现并行编程。因为在很多文档里针对Async和Await这两个关键字的使用都被称为异步编程为了更符合大众的阅读习惯我们使用异步编程这个叫法意思上和并行编程完全一样。关于Async和Await异步编程的功能说明和使用介绍MSDN上有详细文档链接如下http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx其它地方也可以搜索到很多相关文章这里就不再赘述本文主要介绍的是异步编程是如何现实的背后的原理是什么。注意在您阅读下面内容之前请确保已经熟悉了异步编程的基本方法。2. .NET中提供新功能的几种方法在继续之前总结一下.NET中提供新功能的三种方法基于运行时、基于编译器和基于类库。2.1 基于运行时的实现显而易见.NET中大多数功能都是基于运行时实现的。比如的类定义的语法、方法的调用的语法以及所有基本编程语法都有对应的IL代码这也正是定义运行时的内容之一。所以能编译为对应专有IL代码的功能必然是基于运行时实现的。2.2 基于编译器基于编译器的实现最常见的例子就是上下文using和yield。上下文using在VB.NET里干脆就没有对应的语法C#编译器替你做了你在老版本的C#中或VB.NET里要做的工作就是写try、finally和Dispose语句。提供基于编译器的新功能微软不需要修改运行时。2.3 基于类库这个不需要太多解释所有的编程语言都是通过库为开发者提供强大的开发功能的库的丰富程度最终决定一个语言的发展前景。.NET现在常用的运行时只有2.0和4.0两个版本3.0 和3.5都是2.0的运行时4.5的运行时是4.0它是在编译器功能和类库上对4.0的扩展。3. Async和Await的实现前面提到了yield关键字用于简化遍历的实现。如果您熟悉yield这个关键字的应用就会发现await关键字的出现位置、使用方式以及运行逻辑和yield是如此的相似。事实的确如此await和async也是一种基于编译器的功能C#和VB.NET都提供了这个功能不仅如此它在实现原理上也和yield非常像——await/async和yield都被编译器在编译时转化为了状态机。状态机是一种非常常用的编程模式基本上所有的编译器都是基于状态机实现的当访问这篇博文的时候浏览器就是使用状态机将从cnblogs.com服务器上获取的html文本解析为html元素树再绘制到屏幕上。如何发现或者证实这一点呢那就是用.NET的反编译器每当出现新语法但凡好奇者都喜欢用反编译器看一下生成的IL代码究竟是什么样子。在Reflector被收购收费后引来吐槽无数就一直使用JustDecompileTelerik在Reflector收费后立即推出的免费程序使用JustDecompile时需要在该程序的Settings中将Show compiler generated types and members选中。也可以用.NET SDK自带的ILDASM来反编译功能虽然最强大但是只能反编译为IL汇编语言用起来有些不便。首先下载MSDN上的示例Async Sample Example from Asynchronous Programming with Async and Await这是一个简单的WPF应用用于演示Async/Await异步编程主要代码如下 1 public partial class MainWindow : Window2 {3 // Mark the event handler with async so you can use await in it.4 private async void StartButton_Click(object sender, RoutedEventArgs e)5 {6 // Call and await separately.7 //Taskint getLengthTask AccessTheWebAsync();8 You can do independent work here.9 //int contentLength await getLengthTask;
10 int contentLength await AccessTheWebAsync();
11 resultsTextBox.Text
12 String.Format(\r\nLength of the downloaded string: {0}.\r\n, contentLength);
13 }
14
15 // Three things to note in the signature:
16 // - The method has an async modifier.
17 // - The return type is Task or TaskT. (See Return Types p.)
18 // Here, it is Taskint because the return statement returns an integer.
19 // - The method name ends in Async.
20 async Taskint AccessTheWebAsync()
21 {
22 // You need to add a reference to System.Net.Http to declare client.
23 HttpClient client new HttpClient();
24
25 // GetStringAsync returns a Taskstring. That means that when you await the
26 // task youll get a string (urlContents).
27 Taskstring getStringTask client.GetStringAsync(http://msdn.microsoft.com);
28
29 // You can do work here that doesnt rely on the string from GetStringAsync.
30 DoIndependentWork();
31
32 // The await operator suspends AccessTheWebAsync.
33 // - AccessTheWebAsync cant continue until getStringTask is complete.
34 // - Meanwhile, control returns to the caller of AccessTheWebAsync.
35 // - Control resumes here when getStringTask is complete.
36 // - The await operator then retrieves the string result from getStringTask.
37 string urlContents await getStringTask;
38
39 // The return statement specifies an integer result.
40 // Any methods that are awaiting AccessTheWebAsync retrieve the length value.
41 return urlContents.Length;
42 }
43
44 void DoIndependentWork()
45 {
46 resultsTextBox.Text Working . . . . . . .\r\n;
47 }
48 } 然后用JustDecompile打开生成的AsyncFirstExample.exe。类视图如下这时可以看到MainWindow类中多出了两个名称以u003c开头的类这两个类就是状态机类代码中有两个async函数因此生成了两个状态机类。因为编译器转换每个async函数的方式都一样所以下面的内容中都以AccessTheWebAsync这个函数为例来说明该函数对应的状态机类为u003cAccessTheWebAsyncu003ed__4反编译后的C#代码如下 1 [CompilerGenerated]2 // AccessTheWebAsyncd__43 private struct u003cAccessTheWebAsyncu003ed__4 : IAsyncStateMachine4 {5 // 1__state6 public int u003cu003e1__state;7 8 // t__builder9 public AsyncTaskMethodBuilderint u003cu003et__builder;
10
11 // 4__this
12 public MainWindow u003cu003e4__this;
13
14 // client5__5
15 public HttpClient u003cclientu003e5__5;
16
17 // getStringTask5__6
18 public Taskstring u003cgetStringTasku003e5__6;
19
20 // urlContents5__7
21 public string u003curlContentsu003e5__7;
22
23 // u__$awaiter8
24 private TaskAwaiterstring u003cu003eu__u0024awaiter8;
25
26 // t__stack
27 private object u003cu003et__stack;
28
29 void MoveNext()
30 {
31 int t__result 0;
32 TaskAwaiterstring u003cu003eu_u0024awaiter8;
33 try
34 {
35 bool t__doFinallyBodies true;
36 int u003cu003e1_state this.u003cu003e1__state;
37 if (u003cu003e1_state ! -3)
38 {
39 if (u003cu003e1_state 0)
40 {
41 u003cu003eu_u0024awaiter8 this.u003cu003eu__u0024awaiter8;
42 TaskAwaiterstring taskAwaiter new TaskAwaiterstring();
43 this.u003cu003eu__u0024awaiter8 taskAwaiter;
44 this.u003cu003e1__state -1;
45 }
46 else
47 {
48 this.u003cclientu003e5__5 new HttpClient();
49 this.u003cgetStringTasku003e5__6 this.u003cclientu003e5__5.GetStringAsync(http://msdn.microsoft.com);
50 this.u003cu003e4__this.DoIndependentWork();
51 u003cu003eu_u0024awaiter8 this.u003cgetStringTasku003e5__6.GetAwaiter();
52 if (!u003cu003eu_u0024awaiter8.IsCompleted)
53 {
54 this.u003cu003e1__state 0;
55 this.u003cu003eu__u0024awaiter8 u003cu003eu_u0024awaiter8;
56 this.u003cu003et__builder.AwaitUnsafeOnCompletedTaskAwaiterstring, MainWindow.u003cAccessTheWebAsyncu003ed__4(ref u003cu003eu_u0024awaiter8, this);
57 t__doFinallyBodies false;
58 return;
59 }
60 }
61 string result u003cu003eu_u0024awaiter8.GetResult();
62 u003cu003eu_u0024awaiter8 new TaskAwaiterstring();
63 this.u003curlContentsu003e5__7 result;
64 t__result this.u003curlContentsu003e5__7.Length;
65 }
66 }
67 catch (Exception exception)
68 {
69 Exception t__ex exception;
70 this.u003cu003e1__state -2;
71 this.u003cu003et__builder.SetException(t__ex);
72 return;
73 }
74 this.u003cu003e1__state -2;
75 this.u003cu003et__builder.SetResult(t__result);
76 }
77
78 [DebuggerHidden]
79 void SetStateMachine(IAsyncStateMachine param0)
80 {
81 this.u003cu003et__builder.SetStateMachine(param0);
82 }
83 }关于这个类的命名C#编译器命名编译器生成的类和类成员的方式是生成来源名称__后缀或辅助说明信息。尖括号在绝大多数语言中都是运算符不能用作程序中标识符的命名但在IL中标识符都以字符串的形式保存在元数据中通过映射的数字一般是元数据内的本地偏移地址来表示标识符因此对标识符的命名基本没有限制。C#编译器利用这一点在编译器生成的IL代码中通过使用和来明确区分用户写的代码和编译器自动生成的代码。因为和不能用在C#的标识符命名中反编译程序JustDecompile对此做出了处理将转换为u003c转换为u003e也就是Unicode编码。这样反编译出来的程序就能直接拷贝到C#编辑器中使用但是这个版本的JustDecompile存在一个bug就是局部变量中的和并没有被正确的转换为u003c和u003e所以生成的代码还是不能直接拷贝就用的当然这并不影响解读这段代码。类u003cAccessTheWebAsyncu003ed__4实现了接口IAsyncStateMachine从名字可以看出这个接口就是为异步编程定义的。这个接口只有两个方法MoveNext和SetStateMachine一个典型的状态机定义执行下一步和设置状态。用一个简单的例子快速梳理一下状态机的工作过程以帮助理解异步编程的机制一个有1和2两个有效状态的状态机如果状态值为1调用MoveNext时状态机会执行操作A同时将状态值改为2如果状态值为2调用MoveNext时状态机会执行操作B同时将状态值改为3如果状态值为3调用MoveNext时状态机不执行任何操作或抛出异常。在上面的这个简单状态机中调用者不需要知道状态机下一步要干什么它只被告知在某个时候需要调用MoveNext具体干什么由状态机的内部实现决定异步编程就是利用的这种模式通过编译器对代码进行重组将一个await调用前和调用后执行的代码分配到状态机的两个状态中去执行。如果一个async函数中有两个await调用那么生成的状态机就会有3个状态以此类推。如果有循环根据循环的位置不同状态机状态转换更复杂一些。回过头来看异步编程中的异步。在学习使用async/await的时候很多文档包括msdn都刻意提到async/await关键字不会创建新的线程用async关键字写的函数中的代码都在调用线程中执行。这里是最容易混淆的地方严格意义上这个说法不准确异步编程必然是多线程的。msdn文档里提到的不会创建新线程应该是指async函数本身不会直接在新线程中运行。本质上是await调用的异步函数执行完成后回调状态机的MoveNext来执行余下未执行完成的代码await调用的异步函数必然在某个地方——也许是嵌套了很深的一个地方——启动了一个新的工作线程来完成导致我们要使用异步调用的耗时比较长的工作比如网络内容读取。再看u003cAccessTheWebAsyncu003ed__4类的代码u003cu003e1__state这个成员变量很明显就是状态值了在48行到50行当状态只不等于-3也不等于0的时候运行的正好是原始C#代码中await语句前面的代码第52行if (!u003cu003eu_u0024awaiter2.IsCompleted)这里很关键这里正好是异步执行最明显的体现那就是当主线程里DoIndependentWork()运行结束的时候另一个线程里获取http://msdn.microsoft.com页面内容的工作的也可能已经完成了。如果获取页面的工作完成了就可以直接运行下一状态要运行的代码62行到64行原始C#代码中await语句后面的代而不需要进入等待如果获取页面的工作还没有完成执行第54到58行代码将当前状态机与TaskAwaiter绑定同时将状态机的状态值改为0当异步函数在另一个线程中执行完成时TaskAwaiter回调状态机的MoveNext函数这时状态机的状态为0运行62到64行代码完成AcessTheWebAsync函数的工作。可见AcessTheWebAsync函数中原有的代码都被编译器重组到状态机中了那么AcessTheWebAsync函数现在干什么可以猜想到的就是创建状态机实例设置初始状态不等于-3也不等于0和启动状态机。究竟是不是这样来看AcessTheWebAsync反编译出来的C#代码 1 private async Taskint AccessTheWebAsync()2 {3 HttpClient httpClient new HttpClient();4 Taskstring stringAsync httpClient.GetStringAsync(http://msdn.microsoft.com);5 this.DoIndependentWork();6 string str await stringAsync;7 string str1 str;8 int length str1.Length;9 return length;
10 }似乎函数AcessTheWebAsync的代码和原始的代码一样编译器并没有做修改真的是这样吗答案是否定的原因是JustDecompile这个反编译器太强大了它竟然将C#编译器转换的代码重新还原成async/await语法的代码了。所以这里我们只能看IL代码了切换到IL代码可以看到AcessTheWebAsync编译后的最终的代码如下 1 .method private hidebysig instance class [mscorlib]System.Threading.Tasks.Task1int32 AccessTheWebAsync () cil managed 2 {3 .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() (4 01 00 00 005 )6 .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) (7 01 00 34 41 73 79 6e 63 46 69 72 73 74 45 78 618 6d 70 6c 65 2e 4d 61 69 6e 57 69 6e 64 6f 77 2b9 3c 41 63 63 65 73 73 54 68 65 57 65 62 41 73 79
10 6e 63 3e 64 5f 5f 34 00 00
11 )
12 .locals init (
13 [0] valuetype AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4 V_0,
14 [1] class [mscorlib]System.Threading.Tasks.Task1int32 V_1,
15 [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32 V_2
16 )
17
18 IL_0000: ldloca.s V_0
19 IL_0002: ldarg.0
20 IL_0003: stfld class AsyncFirstExample.MainWindow AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4::4__this
21 IL_0008: ldloca.s V_0
22 IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32 valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32::Create()
23 IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32 AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4::t__builder
24 IL_0014: ldloca.s V_0
25 IL_0016: ldc.i4.m1
26 IL_0017: stfld int32 AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4::1__state
27 IL_001c: ldloca.s V_0
28 IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32 AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4::t__builder
29 IL_0023: stloc.2
30 IL_0024: ldloca.s V_2
31 IL_0026: ldloca.s V_0
32 IL_0028: call instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32::Startvaluetype AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4(!!0)
33 IL_002d: ldloca.s V_0
34 IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32 AsyncFirstExample.MainWindow/AccessTheWebAsyncd__4::t__builder
35 IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task1int32 valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1int32::get_Task()
36 IL_0039: stloc.1
37 IL_003a: br.s IL_003c
38
39 IL_003c: ldloc.1
40 IL_003d: ret
41 }仔细看这段IL汇编代码与原始的C#版的AcessTheWebAsync函数相比几乎没有任何相似之处只有函数的声明相同这就是编译器转换的结果。人工将这段IL汇编代码反编译成C# 1 [System.Diagnostics.DebuggerStepThrough()]2 [System.Runtime.CompilerServices.AsyncStateMachine(typeof(u003cAccessTheWebAsyncu003ed__4))]3 private Taskint AccessTheWebAsync()4 {5 u003cAccessTheWebAsyncu003ed__4 V_0;6 Taskint V_1;7 System.Runtime.CompilerServices.AsyncTaskMethodBuilderint V_2;8 9 V_0.u003cu003e4__this this;
10 V_0.u003cu003et__builder System.Runtime.CompilerServices.AsyncTaskMethodBuilderint.Create();
11 V_0.u003cu003e1__state -1;
12 V_2 V_0.u003cu003et__builder;
13 V_2.Start(ref V_0);
14 V_1 V_2.Task;
15 return V_1;
16 }到这里已经非常清楚了AcessTheWebAsync函数首先创建状态机的实例因为状态机类是Struct类型不需要new然后设置相关属性状态机的初始状态值被设置为-1符合之前期望的范围最后启动状态机Start方法内部会调用一次MoveNext运行结束后返回Task。多个async函数之间的调用就是多个状态机的组合运行。4. 创建一个真正异步的异步函数前面提到await语句await到最后必然调用了一个启动了新线程的完成实际工作的真正异步的异步函数那么如何自己定义一个这样的函数呢其实很简单使用System.Threading.Tasks.Task类就可以创建这样一个函数示例代码如下 private async void Button_Click(object sender, RoutedEventArgs e){resultsTextBox.Text String.Format(\r\nMyAsync({0}).\r\n,Thread.CurrentThread.ManagedThreadId); while (true)resultsTextBox.Text String.Format(\r\nMyAsync({0}): {1}.\r\n, Thread.CurrentThread.ManagedThreadId, await MyAsync());}public Taskstring MyAsync(){var t new Taskstring((str) {var dt DateTime.Now;Thread.Sleep(4000);return String.Format(({0}){1} - {2}, Thread.CurrentThread.ManagedThreadId, dt, DateTime.Now);}, null);t.Start();return t;}运行结果如下这个程序是在上述msdn提供的示例的基础上向界面中加了一个ID为Button的按钮它的事件处理函数为Button_ClickMyAsync就是我们要创建的函数。在这个真正异步的函数里却看不到Aysnc和Await的影子。由此可见Aysnc和Await是用来组织异步函数的调用的实现异步代码和同步代码间的无缝交互。5. 结论 在.NET 4.5中引入的Async和Await两个新的关键字后用户能以一种简洁直观的方式实现异步编程。甚至都不需要改变代码的逻辑结构就能将原来的同步函数改造为异步函数。在内部实现上Async和Await这两个关键字由编译器转换为状态机通过System.Threading.Tasks中的并行类实现代码的异步执行。 异步编程中的最佳做法https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming