新进阶的程序员可能对async、await用得比较多,却对之前的异步了解甚少。本人就是此类,因此打算回顾学习下异步的进化史。
APM
APM 异步编程模型,Asynchronous Programming Model
早在C#1的时候就有了APM。虽然不是很熟悉,但是多少还是见过的。就是那些类是BeginXXX和EndXXX的方法,且BeginXXX返回值是IAsyncResult接口。
在正式写APM示例之前我们先给出一段同步代码:
button1_Click( +</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> request = WebRequest.Create(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;"><a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>s://github.com/</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">);<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//为了更好的演示<a href="https://www.jb51.cc/tag/xiaoguo/" target="_blank" >效果</a>,<a href="https://m.jb51.cc/tag/women/" target="_blank" >我们</a><a href="https://m.jb51.cc/tag/shiyong/" target="_blank" >使用</a>网速比较慢的外网</span>request.GetResponse();</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">发送请求 </span>
<span >
DeBUG.Writeline(
label1.Text = <span >"<span >执行完毕!<span >"<span >;
}
【说明】为了更好的演示异步效果,这里我们使用winform程序来做示例。
【效果图】
看图得知:
我们在执行方法的时候页面出现了“假死”,拖不动了。我们看到打印结果,方法调用前和调用后线程ID都是9(也就是同一个线程)下面我们再来演示对应的异步方法:()
button2_Click( DeBUG.Writeline( +</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> request = WebRequest.Create(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;"><a href="https://m.jb51.cc/tag/http/" target="_blank" >http</a>s://github.com/</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">);request.BeginGetResponse(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span> AsyncCallback(t =><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//执行完成后的回调</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">{ </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> response = request.EndGetResponse(t); <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> stream = response.GetResponseStream();<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;"><a href="https://www.jb51.cc/tag/huoqu/" target="_blank" >获取</a>返回数据流 </span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">using</span> (StreamReader reader = <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> StreamReader(stream)) { StringBuilder sb </span>= <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> StringBuilder(); </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">while</span> (!<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">reader.EndOfStream) { </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> content =<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> reader.Read<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(); sb.Append(content); } De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">【De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>】</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span> + sb.ToString().Trim().Substring(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800080;">0</span>,<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800080;">100</span>) + <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">...</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span>);<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">只取返回<a href="https://www.jb51.cc/tag/neirong/" target="_blank" >内容</a>的前100个字符 </span> De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">【De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>】异步线程<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>:</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span> +<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> Thread.CurrentThread.ManagedThread<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>); label1.Invoke((Action)(() </span>=> { label1.Text = <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">执行完毕!</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span>; }));<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;"><a href="https://m.jb51.cc/tag/zheli/" target="_blank" >这里</a>跨线程访问UI需<a href="https://www.jb51.cc/tag/yaozuo/" target="_blank" >要做</a>处理</span>
<span > }
}),
De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">【De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>】主线程<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>:</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span> +<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> Thread.CurrentThread.ManagedThread<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>);
}
【效果图】
看图得知:
启用异步方法并没有是UI界面卡死异步方法启动了另外一个ID为12的线程上面代码执行顺序:
前面我们说过,APM的BebinXXX必须返回IAsyncResult接口。那么接下来我们分析IAsyncResult接口:
首先我们看:
确实返回的是IAsyncResult接口。那IAsyncResult到底长的什么样子?:
并没有想象中的那么复杂嘛。我们是否可以尝试这实现这个接口,然后显示自己的异步方法呢?
首先定一个类MyWebRequest,然后继承IAsyncResult:
{</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> WaitHandle AsyncWaitHandle{ </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">get</span> { <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">throw</span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> NotImplementedException(); }}</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c</span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">bo<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a></span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> CompletedSynchronously{ </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">get</span> { <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">throw</span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> NotImplementedException(); }}</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">pub<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>c</span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">bo<a href="https://m.jb51.cc/tag/ol/" target="_blank" >ol</a></span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> IsCompleted{ </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">get</span> { <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">throw</span> <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> NotImplementedException(); }}
}
这样肯定是不能用的,起码也得有个存回调函数的属性吧,下面我们稍微改造下:
然后我们可以自定义APM异步模型了:
asyncResult = MyWebRequest(callback, request = WebRequest.Create( Thread(() => (StreamReader sr = str =}).Start();</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">return</span> asyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t;<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//返</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">回<a href="https://www.jb51.cc/tag/yige/" target="_blank" >一个</a>IAsyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t</span>
<span >}
<span >public <span >string<span > MyEndXX(IAsyncResult asyncResult){
MyWebRequest result = asyncResult <span >as<span > MyWebRequest;
<span >return<span > result.Result;
}
调用如下:
button4_Click( + AsyncCallback(t => result = + result.Trim().Substring(,) + + +效果图:
我们看到自己实现的效果基本上和系统提供的差不多。
启用异步方法并没有是UI界面卡死异步方法启动了另外一个ID为11的线程【总结】
个人觉得APM异步模式就是启用另外一个线程执行耗时任务,然后通过回调函数执行后续 *** 作。
APM还可以通过其他方式获取值,如:
(!{ Thread.Sleep( stream2 = request.EndGetResponse(asyncResult).GetResponseStream();或
stream2 = request.EndGetResponse(asyncResult).GetResponseStream();补充:如果是普通方法,我们也可以通过委托异步:
func = Func<,>(t => + t + </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">var</span> asyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t = func.BeginInvoke(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">张三</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span>,t =><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> { </span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">string</span> str =<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> func.EndInvoke(t); De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(str); },</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">n<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>l</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">);
}
EAPEAP 基于事件的异步模式,Event-based Asynchronous Pattern
此模式在C#2的时候随之而来。
先来看个EAP的例子:
button3_Click( + Backgroun<a href="https://m.jb51.cc/tag/DW/" target="_blank" >DW</a>orker worker </span>= <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> Backgroun<a href="https://m.jb51.cc/tag/DW/" target="_blank" >DW</a>orker(); worker.DoWork </span>+= <span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">new</span> DoWorkEventHandler((s1,s2) =><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> { Thread.Sleep(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800080;">2000</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">); De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">【De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>】异步线程<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>:</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span> +<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> Thread.CurrentThread.ManagedThread<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>); });</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;">//</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #008000;"><a href="https://www.jb51.cc/tag/zhuce/" target="_blank" >注册</a>事件来实现异步</span> worker.RunWorkerAsync(<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #0000ff;">this</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;">); De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">【De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>】主线程<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>:</span><span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #800000;">"</span> +<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> Thread.CurrentThread.ManagedThread<a href="https://m.jb51.cc/tag/ID/" target="_blank" >ID</a>);
}
【效果图】
【特征】
通过事件的方式注册回调函数通过 XXXAsync方法来执行异步调用例子很简单,但是和APM模式相比,是不是没有那么清晰透明。为什么可以这样实现?事件的注册是在干嘛?为什么执行RunWorkerAsync会触发注册的函数?
感觉自己又想多了...
我们试着反编译看看源码:
只想说,这么玩,有意思吗?
TAPTAP 基于任务的异步模式,Task-based Asynchronous Pattern
到目前为止,我们觉得上面的APM、EAP异步模式好用吗?好像没有发现什么问题。再仔细想想...如果我们有多个异步方法需要按先后顺序执行,并且需要得到所有返回值。
首先定义三个委托:
Func<,> Func<,>(t => + Func<,> Func<,>(t => + Func<,> Func<,>(t => +然后按照一定顺序执行:
str1 = .Empty,str2 = .Empty,str3 = = ,asyncResult2 = ,asyncResult3 = = func1().BeginInvoke(,t =>= += func2().BeginInvoke(,a =>= += func3().BeginInvoke(,s =>= +asyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t1.AsyncWaitHandle.WaitOne();asyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t2.AsyncWaitHandle.WaitOne();asyncRes<a href="https://m.jb51.cc/tag/ul/" target="_blank" >ul</a>t3.AsyncWaitHandle.WaitOne();De<a href="https://m.jb51.cc/tag/BUG/" target="_blank" >BUG</a>.Write<a href="https://m.jb51.cc/tag/li/" target="_blank" >li</a>ne(str1 </span>+ str2 +<span https://m.jb51.cc/tag/color/" target="_blank" >color</a>: #000000;"> str3);
}
<span >除了难看、难读一点好像也没什么 。不过真的是这样吗?
asyncResult2是null?由此可见在完成第一个异步 *** 作之前没有对asyncResult2进行赋值,asyncResult2执行异步等待的时候报异常。那么如此我们就无法控制三个异步函数,按照一定顺序执行完成后再拿到返回值。
是的,现在该我们的TAP登场了。
只需要调用Task类的静态方法Run,即可轻轻松松使用异步。
获取返回值:
task1 = Task<>.Run(() => + value = task1.Result;Console.Writeline( + Thread.CurrentThread.ManagedThreadID);现在我们处理上面多个异步按序执行:
Console.Writeline( + str1 = .Empty,str3 = task1 = Task.Run(() =>= +=>= +=>= +Thread.Sleep(<span >2500);<span >//<span >其他逻辑代码<span >
task1.Wait();
DeBUG.Writeline(str1
+ str2 +<span > str3);Console.Writeline(<span >"<span >【DeBUG】主 线程ID:<span >" + Thread.CurrentThread.ManagedThreadID);
[效果图]
我们看到,结果都得到了,且是异步按序执行的。且代码的逻辑思路非常清晰。
延伸思考WaitOne完成等待的原理
异步为什么会提升性能
线程的使用数量和cpu的使用率有必然的联系吗
问题1:WaitOne完成等待的原理
在此之前,我们先来简单的了解下多线程信号控制autoresetEvent类。
_asyncWaitHandle = autoresetEvent(此代码会在 的地方会一直等待下去。除非有另外一个线程执行 的set方法。
_asyncWaitHandle = autoresetEvent(如此,到了 就可以直接执行下去。没有有任何等待。
现在我们对APM 异步编程模型中的 等待是不是知道了点什么呢。我们回头来实现之前自定义异步方法的异步等待。