关于异步的初步认识

  在当初试用多线程的时候发现多线程能减轻或消除大量繁杂操做或过长等待时间形成的停滞感(就是线程阻塞)。后来发现使用异步操做也能达到相同的效果。可是二者之间是有区别的,以前在知识库里看了一些文章,我也记录了一下(有人云亦云的感受),顺便也摆出一些我的观点。数据库

  多线程和异步虽然均可以减轻或消除线程阻塞而形成的停滞感,可是二者的本质上是有区别的网络

  多线程是软件级别上的机制,在微观上它是分配CPU的时间片给某个进程中的各条线程,得到时间片的线程就能够处理它的任务,也就是执行代码。在其中负责调度CPU资源的就是操做系统,因此多线程是否能实现取决于操做系统,现今绝大部分操做系统都是多线程的系统,在DOS下是不支持多线程的。多线程

  异步则是硬件级别上的机制,在大学学习《计算机组成原理》时,就提过硬件的DMA(Direct Memory Access,直接内存存取),它是让一些计算机的外部设备(网卡,磁盘等)在不用消耗CPU时间的状况下,直接与内存交互进行数据读写,在此期间CPU能够着手其余事情,在IO完毕后才把调度权还给CPU。因而可知,过中不须要操做系统的支持,因此按照这样的思路去想,只须要计算机的硬件支持的话,在DOS中也能实现异步操做(这个在网上看到的,实际上我也没搞个DOS去实践)。异步

  由上述的区别能够推断出他们的适用场合,异步是硬件层面的,一些关于硬件层面上的操做用起异步来会适合一些,例如文件的读写操做,数据库访问,网络访问等;多线程是软件层面的,CPU是否把时间片分配给当前线程,与可否让外部设备直接访问内存关系不大,相同的操做也是一样要消耗相同的CPU时间,相比起来,一些要CPU花费大量时间去处理的操做用多线程去实现会恰当一点。学习

  理论上就如前面所说的,可是回到.NET Framework里面,貌似是另外一回事了。经过运行如下代码测试

 1         static void Main(string[] args)
 2         {
 3             PrintThreadInfo("Main");
 4             Action act = delegate() { PrintThreadInfo("Action"); };
 5             IAsyncResult ir = act.BeginInvoke(new AsyncCallback(AsyncCallback), act);
 6             
 7             Console.ReadLine();
 8         }
 9 
10         static void AsyncCallback(IAsyncResult result)
11         {
12             PrintThreadInfo("Callback");
13             Action caller = result.AsyncState as Action;
14             caller.EndInvoke(result);
15         }
16 
17         static void PrintThreadInfo( string host )
18         {
19             Console.WriteLine(string.Format( " {2} call: Current Thread Id is {0}, it {1} in threadpool",Thread.CurrentThread.ManagedThreadId,Thread.CurrentThread.IsThreadPoolThread?"is":"isn't",host ));
20         }

 

 

  发现异步操做实际上仍是利用了多线程,并且这条新开辟的线程是来源于线程池ThreadPool的。在MSDN上翻找了一下BeginInvoke的解释的确是异步调用的。它确确实实属于APM模式(BeginXXX/EndXXX)。那个再观察一下别的类在APM模式下的工做状况spa

 1         static void Main(string[] args)
 2         {
 3             PrintThreadInfo("Main");
 4             byte[] datas = Encoding.ASCII.GetBytes("hello world");
 5             FileStream fs = new FileStream("abc.txt", FileMode.Create, FileAccess.Write,FileShare.Write, 1024, FileOptions.Asynchronous);
 6             fs.BeginWrite(datas, 0, datas.Length, new System.AsyncCallback(AsyncCallback), fs);
 7 
 8 
 9             Console.ReadLine();
10         }
11 
12         static void AsyncCallback(IAsyncResult result)
13         {
14             PrintThreadInfo("Callback");
15             FileStream caller = result.AsyncState as FileStream;
16             caller.EndWrite(result);
17             caller.Close();
18             caller.Dispose();
19         }

  这里选取了FileStream做例子,但从结果能够看出,纵使确确实实是异步操做,确确实实是文件写入,可是仍然是有调用了线程池,使用了线程。看回第一个例子的结果BeginInvoke和回调方法都是在同一条线程上执行的,相比起第二个例子就有个局限性,在BeginWrite调用的时候没办法看查看是否有使用线程去进行写操做,第二行信息是在回调时显示出来的。那这里是否和上一个例子同样二者都在同一个线程上运行呢?我有个比较拙劣的办法以下图所示操作系统

  第一个例子中的状况线程

第二个例子中的状况code

  虽然这样断点测试貌似有点偏差,不知有否说服力。对比之下仍是能够看出第一个例子它调用异步的时候就建立了线程,严格意义上并不属于异步,第二个例子调用异步方法时没有建立线程,直到回传的时候才去建立了线程。能够初步证明在调用BeginWrite的时候并无去建立线程,确实是使用了DMA机制,确确实实是异步调用了。

  参考赵劼老师说的话,CLR会(经过Windows API)发出一个IRP(I/O Request Packet)。当设备准备稳当,就会找出一个它“最想处理”的IRP(例如一个读取离当前磁头最近的数据的请求)并进行处理,处理完毕后设备将会(经过Windows)交还一个表示工做完成的IRP。CLR会为每一个进程建立一个IOCP(I/O Completion Port)并和Windows操做系统一块儿维护。IOCP中一旦被放入表示完成的IRP以后(经过内部的ThreadPool.BindHandle完成),CLR就会尽快分配一个可用的线程用于继续接下去的任务。

  我的理解就是CLR与底层硬件交互,让相应设备的再也不消耗CPU去设备访存。正如文章开头的理论部分所言,异步属于硬件方面的,因此一些委托的异步调用BeginInvoke并是假异步,例如上面文件操做的异步才是真异步,能实现真异步的有如下方法

  • FileStream操做:BeginRead、BeginWrite(只有构造FileStream时传入FileOptions.Asynchronous参数才能获取真正的异步操做,不然仍然是假异步)。
  • DNS操做:BeginGetHostByName、BeginResolve。
  • Socket操做:BeginAccept、BeginConnect、BeginReceive等等。
  • WebRequest操做:BeginGetRequestStream、BeginGetResponse。
  • SqlCommand操做:BeginExecuteReader、BeginExecuteNonQuery等等(要在链接字符串中把Asynchronous Processing设为true,不然调用异步方法时会抛异常)。
  • WebServcie调用操做:例如.NET 2.0或WCF生成的Web Service Proxy中的BeginXXX方法、WCF中ClientBase<TChannel>的InvokeAsync方法。

  最后要记录一下的就是使用APM模式的时候必定要调用回调方法或者EndXXX,不然线程资源没法回收,有可能致使系统崩溃。

  以上文章有参考赵劼老师的《正确使用异步操做》,还有一部分是在下的拙见,各位以为在下有什么说错的欢迎批评指正,有什么建议或意见尽管说说。谢谢!

相关文章
相关标签/搜索