Async和Await异步编程的原理

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被收购收费后(引来吐槽无数),就一直使用JustDecompile(Telerik在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             //Task<int> 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 Task<T>. (See "Return Types" p.)
18         //    Here, it is Task<int> because the return statement returns an integer.
19         //  - The method name ends in "Async."
20         async Task<int> 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 Task<string>. That means that when you await the
26             // task you'll get a string (urlContents).
27             Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");
28 
29             // You can do work here that doesn't rely on the string from GetStringAsync.
30             DoIndependentWork();
31 
32             // The await operator suspends AccessTheWebAsync.
33             //  - AccessTheWebAsync can't 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         // <AccessTheWebAsync>d__43         private struct u003cAccessTheWebAsyncu003ed__4 : IAsyncStateMachine4         {5             // <>1__state6             public int u003cu003e1__state;7 8             // <>t__builder9             public AsyncTaskMethodBuilder<int> u003cu003et__builder;
10 
11             // <>4__this
12             public MainWindow u003cu003e4__this;
13 
14             // <client>5__5
15             public HttpClient u003cclientu003e5__5;
16 
17             // <getStringTask>5__6
18             public Task<string> u003cgetStringTasku003e5__6;
19 
20             // <urlContents>5__7
21             public string u003curlContentsu003e5__7;
22 
23             // <>u__$awaiter8
24             private TaskAwaiter<string> u003cu003eu__u0024awaiter8;
25 
26             // <>t__stack
27             private object u003cu003et__stack;
28 
29             void MoveNext()
30             {
31                 int <>t__result = 0;
32                 TaskAwaiter<string> 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                             TaskAwaiter<string> taskAwaiter = new TaskAwaiter<string>();
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.AwaitUnsafeOnCompleted<TaskAwaiter<string>, 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 TaskAwaiter<string>();
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 Task<int> AccessTheWebAsync()2         {3             HttpClient httpClient = new HttpClient();4             Task<string> 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.Task`1<int32> 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/'<AccessTheWebAsync>d__4' V_0,
14             [1] class [mscorlib]System.Threading.Tasks.Task`1<int32> V_1,
15             [2] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> 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/'<AccessTheWebAsync>d__4'::'<>4__this'
21         IL_0008: ldloca.s V_0
22         IL_000a: call valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
23         IL_000f: stfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> AsyncFirstExample.MainWindow/'<AccessTheWebAsync>d__4'::'<>t__builder'
24         IL_0014: ldloca.s V_0
25         IL_0016: ldc.i4.m1
26         IL_0017: stfld int32 AsyncFirstExample.MainWindow/'<AccessTheWebAsync>d__4'::'<>1__state'
27         IL_001c: ldloca.s V_0
28         IL_001e: ldfld valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> AsyncFirstExample.MainWindow/'<AccessTheWebAsync>d__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.AsyncTaskMethodBuilder`1<int32>::Start<valuetype AsyncFirstExample.MainWindow/'<AccessTheWebAsync>d__4'>(!!0&)
33         IL_002d: ldloca.s V_0
34         IL_002f: ldflda valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> AsyncFirstExample.MainWindow/'<AccessTheWebAsync>d__4'::'<>t__builder'
35         IL_0034: call instance class [mscorlib]System.Threading.Tasks.Task`1<int32> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::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 Task<int> AccessTheWebAsync()4         {5             u003cAccessTheWebAsyncu003ed__4 V_0;6             Task<int> V_1;7             System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int> V_2;8 9             V_0.u003cu003e4__this = this;
10             V_0.u003cu003et__builder = System.Runtime.CompilerServices.AsyncTaskMethodBuilder<int>.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 Task<string> MyAsync(){var t = new Task<string>((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_Click,MyAsync就是我们要创建的函数。

在这个真正异步的函数里却看不到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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/302766.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

细数那些让人难以抗拒的经典数学书

如果有人不相信数学是简单的,那是因为他们没有意识到人生有多复杂。——冯诺依曼近期有关数学的好消息还是蛮多的&#xff0c;先有阿里巴巴举办全国数学竞赛&#xff0c;奖金百万&#xff0c;只为爱好数学的你。快来看看下面这些竞赛试题&#xff0c;我想应该难不倒我们的小伙伴…

java对jar包的复制_Java安全之jar包调试技巧

Java安全之jar包调试技巧调试程序首先还是创建一个工程&#xff0c;将jar包导入进来调试模式的参数启动中需要加入特定参数才能使用debug模式&#xff0c;并且需要开放调试端口JDK5-8:-agentlib:jdwptransportdt_socket,servery,suspendy,address5005JDK9&#xff1a;-agentlib…

一次Redis client组件性能分析

BeetleX也扩展了RedisClient驱动&#xff0c;写这些高并发应用的驱动性能测试分析是必不可少的。在最近一次测试中发现测试采样度不足&#xff0c;引起的一些问题&#xff1b;通过这一次的问题也警醒一下自己在以后设计上要考虑更多细节的特性需求。发现问题在写组件的时候往往…

男生追女生的超强数学建模分析

全世界有3.14 % 的人已经关注了数据与算法之美问题分析男生追女生&#xff0c;对男生来说最重要的是学习、爱情两不误。因此我们引进男生的学业成绩函数Y(t)。首先&#xff0c;我们不考虑男生的追求攻势&#xff0c;则影响该函数的因素主要是两个人的关系程度。为了便于分析&am…

java设计模式face_java设计模式之-------原型模式

一、模式定义用原型实例指定要创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象。二、模式场景假设此处结合23中设计模式记忆篇作出假设&#xff0c;不关心逻辑是否合理。柳岩在跳舞的时候烧伤了脸&#xff0c;再也回不到原来的型状。他就克隆一张脸。类图如下&#…

你被这些网络迷题难倒过吗?

全世界有3.14 % 的人已经关注了数据与算法之美消失的正方形这是数学游戏大师马丁加德纳在《从惊讶到思考》一书中提到过的例子。重新摆放分割的小块图形后&#xff0c;上面的正方形中少了一个小方格&#xff0c;它去了哪里&#xff1f;我们不妨实际操作一下&#xff0c;做两个全…

温故知新,.Net Core遇见Blazor(FluentUI),属于未来的SPA框架

什么是BlazorBlazor是一个使用.NET生成交互式客户端WebUI的框架:使用C#代替JavaScript来创建信息丰富的交互式UI。共享使用.NET编写的服务器端和客户端应用逻辑。将UI呈现为HTML和CSS&#xff0c;以支持众多浏览器&#xff0c;其中包括移动浏览器。与新式托管平台&#xff08;如…

看来要先拒绝37%的女人,才能找到真爱

全世界有3.14 % 的人已经关注了数据与算法之美在每期《非诚勿扰》节目上&#xff0c;面对一位位男嘉宾&#xff0c;24位单身女生要做出不止一次“艰难的决定”&#xff1a;到底要不要继续亮灯&#xff1f;把灯灭掉意味着放弃了这一次机会&#xff0c;继续亮灯则有可能结束节目之…

.Net日志之nlog

1. 介绍NLog是适用于各种.NET平台&#xff08;包括.NET标准&#xff09;的灵活&#xff0c;免费的日志记录平台&#xff0c;支持数据库、文件、控制台。2. 输入到文件2.1 引用nuget包<PackageReference Include"NLog" Version"4.7.6" /><PackageR…

为了证明自己有多能喝,理工科的学生竟做出这种事

全世界有3.14 % 的人已经关注了数据与算法之美当酒鬼止不住地说“我没有醉”、“再来一杯”的时候&#xff0c;他基本上已经醉得不行了。此时&#xff0c;他会说出一堆毫无逻辑的话&#xff0c;让旁人听了不知所云。这就是最为常见的酒鬼形象了。直到某一天&#xff0c;死理性派…

65 + iPhone应用程序网站创意设计灵感(上篇)

这里是一个巨大的艺术创作的iPhone APP网站设计的灵感收集。这些网站设计&#xff0c;特殊的配色方案&#xff0c;详细的图标和高品质的图形使用&#xff0c;如此精彩。希望能给你带来灵感。 Analog App Courier App Thermo App Faces Ecoki Motionoto Barista Moneybook App L…

php程序员跟java一样吗,【后端开辟】php程序员能够转java吗?

php顺序员可以转java吗&#xff1f;可以。关于专业顺序员来讲&#xff0c;在差别的开辟场景下采纳差别的编程言语是比较罕见的事变&#xff0c;所以许多顺序员在事情一段时候以后都邑控制多种差别的编程言语&#xff0c;比方不少Web顺序员都邑同时控制Java、PHP、C#等编程言语。…

你管这叫线程安全?

来来来&#xff0c;面试八股文&#xff1f;今天我们从什么叫"线程安全"聊起&#xff1f;文末"边吃边聊"。今日份的干粮&#xff1a;1.什么叫线程安全&#xff1f;2.线程安全与变量的关系&#xff1f;•变量又与堆/栈/静态存储区有密切关系什么叫线程安全&a…

经济学家告诉你,胸越大的女生越会花钱!

全世界有3.14 % 的人已经关注了数据与算法之美大概两周前回加拿大的航班上&#xff0c;我在机舱尾部跟一位空姐聊了很久。当空姐小妹双眼向下一瞥&#xff0c;略微停顿之后问我“班组要在这里呆四天&#xff0c;不知道该干什么去”的时候&#xff0c;我认认真真的为自己叹了口气…

讲100个科学道理,不如做这些有趣的理科实验!

玩具和学习看似是两个对立的东西&#xff0c;孩子天性爱玩&#xff0c;家长却希望孩子能多学习。不一定非要啃课本才能汲取知识&#xff0c;有时候&#xff0c;在轻松有趣的游戏中也能学到课堂上学不到的知识。让学习变得有趣、高效——给孩子讲100个科学道理&#xff0c;不如带…

《权力》读书笔记

1 为什么权力只为某些人所拥有我们生活在一个充满等级制度的世界里&#xff0c;等级的存在就意味着竞争&#xff0c;而且越往上&#xff0c;职位就越少&#xff0c;竞争也就越激烈。只有了解权力的原则&#xff0c;并愿意运用它们的人&#xff0c;才可以参与这样的竞争&#xf…

嘘!偷偷教你们一个在双十一省钱的办法!

各位小可爱&#xff0c;大可爱&#xff0c;大家晚上好~一年一度的双11即将到来&#xff01;&#xff01;&#xff01;小木冒着被砍头的风险为大家争取了好多好多福利绝对比平时更优惠更省钱哦&#xff01;&#xff01;&#xff01;活动时间&#xff1a;10月27日-11月11日&#…

ML.NET 示例:图像分类模型训练-首选API(基于原生TensorFlow迁移学习)

ML.NET 版本API 类型状态应用程序类型数据类型场景机器学习任务算法Microsoft.ML 1.5.0动态API最新控制台应用程序和Web应用程序图片文件图像分类基于迁移学习的TensorFlow模型再训练进行图像分类DNN架构&#xff1a;ResNet、InceptionV3、MobileNet等问题图像分类是深度学习学…

算法证明:女生遇到心动的男人一定要追!

全世界有3.14 % 的人已经关注了数据与算法之美我来讲恋爱中的博弈&#xff0c;不&#xff0c;我来讲恋爱中的算法&#xff0c;不&#xff0c;我来讲算法&#xff01;&#xff01;有个著名的问题&#xff0c;叫做 stable matching。早年是一个可爱的俄罗斯老头在图论课上教我的&…

记一次 .NET 某教育系统 异常崩溃分析

一&#xff1a;背景 1. 讲故事这篇文章起源于 搬砖队大佬 的精彩文章 WinDBg定位asp.net mvc项目异常崩溃源码位置 &#xff0c;写的非常好&#xff0c;不过美中不足的是通览全文之后&#xff0c;总觉得有那么一点不过瘾&#xff0c;就是没有把当时抛异常前的参数给找出来。。。…