首先来看一个简单的例子:

  1. 小明在烧水,等水烧开以后,将开水灌入热水瓶,然后开始整理家务
  2. 小文在烧水,在烧水的过程中整理家务,等水烧开以后,放下手中的家务活,将开水灌入热水瓶,然后继续整理家务

这也是日常生活中很常见的情形,小文的办事效率明显要高于小明。从C#程序执行的角度考虑,小明使用的同步处理方式,而小文则使用的异步处理方式。

同步处理方式下,事务是按顺序一件一件处理的;而异步方式则是,将子操作从主操作中分离出来,主操作继续进行,子操作在完成处理的时候通知主操作。

在C#中,异步通过委托来完成。请看下面的例子:

  1. class Program
  2. {
  3. static TimeSpan Boil()
  4. {
  5. Console.WriteLine("水壶:开始烧水...");
  6. Thread.Sleep(6000);
  7. Console.WriteLine("水壶:水已经烧开了!");
  8. return TimeSpan.MinValue;
  9. }
  10. delegate TimeSpan BoilingDelegate();
  11. static void Main(string[] args)
  12. {
  13. Console.WriteLine("小文:将水壶放在炉子上");
  14. BoilingDelegate d = new BoilingDelegate(Boil);
  15. IAsyncResult result = d.BeginInvoke(BoilingFinishedCallback, null);
  16. Console.WriteLine("小文:开始整理家务...");
  17. for (int i = 0; i < 20; i++)
  18. {
  19. Console.WriteLine("小文:整理第{0}项家务...", i + 1);
  20. Thread.Sleep(1000);
  21. }
  22. }
  23. static void BoilingFinishedCallback(IAsyncResult result)
  24. {
  25. AsyncResult asyncResult = (AsyncResult)result;
  26. BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;
  27. del.EndInvoke(result);
  28. Console.WriteLine("小文:将热水灌到热水瓶");
  29. Console.WriteLine("小文:继续整理家务");
  30. }
  31. }

上面的例子是一个最简单的异步调用的例子,没有对异步调用函数做任何参数传递以及返回值校验。这个例子反映了小文烧水的流程,首先小文将水壶放在炉子上,在定义好委托以后,就使用BeginInvoke方法开始异步调用,即让水壶开始烧水,于是小文便开始整理家务。水烧开后,C#的异步模型会触发由BeginInvoke方法所指定的回调函数,也就是水烧开后的处理逻辑由这个回调函数定义,此时小文将水灌入热水瓶并继续整理家务。

由此可见,在C#中实现异步调用其实并不复杂,首先创建一个异步处理函数,并针对其定义一个委托然后在调用函数的时候,使用委托的BeginInvoke方法,指定在函数处理完成时的回调函数(如果不需要对完成事件做处理,可以给null值),并指定所需的参数(如果没有参数,也可以给null值);最后在回调函数中处理完成事件。

请注意上例回调函数中的EndInvoke调用,EndInvoke会使得调用线程阻塞,直到异步函数处理完成。显然,紧接在BeginInvoke后面的EndInvoke使用方式与同步调用等价。

EndInvoke调用的返回值也就是异步处理函数的返回值。我们把程序稍作修改,将Boil方法改成下面的形式:

  1. static TimeSpan Boil()
  2. {
  3. DateTime begin = DateTime.Now;
  4. Console.WriteLine("水壶:开始烧水...");
  5. Thread.Sleep(6000);
  6. Console.WriteLine("水壶:水已经烧开了!");
  7. return DateTime.Now - begin;
  8. }

然后将BoilingFinishedCallback改成下面的形式:

  1. static void BoilingFinishedCallback(IAsyncResult result)
  2. {
  3. AsyncResult asyncResult = (AsyncResult)result;
  4. BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;
  5. Console.WriteLine("(烧水一共用去{0}时间)", del.EndInvoke(result));
  6. Console.WriteLine("小文:将热水灌到热水瓶");
  7. Console.WriteLine("小文:继续整理家务");
  8. }

那么我们就可以在EndInvoke的时候,获得由Boil异步处理函数返回的时间值。事实上,如果定义的BoilingDelegate委托存在参数列表,那么我们也可以在BeginInvoke的时候,将所需的参数传给异步处理函数。BeginInvoke/EndInvoke函数的签名与定义它们的委托签名有关。

注意:在修改后的BoilingFinishedCallback方法中,为了得到委托实例以便获取异步处理函数的返回值,我们采用了下面的转换:

  1. AsyncResult asyncResult = (AsyncResult)result;
  2. BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;

这样才能获得调用异步处理函数的委托的实体。

.NET处理异步函数调用,事实上是通过线程来完成的。这个过程有以下几个特点:

  • 异步函数由线程完成,这个线程是.NET线程池中的线程
  • 通常情况下,.NET线程池拥有500个线程(当然这个数量可以设置),每当调用BeginInvoke开始异步处理时,异步处理函数就由线程池中的某个线程负责执行,而用户无法控制具体是由哪个线程负责执行
  • 由于线程池中线程数量有限,因此当池中线程被完全占用时,新的调用请求将使函数不得不等待空余线程的出现。此时,程序的效率会有所影响

为了验证这些特点,请看下面的程序:

  1. class Program
  2. {
  3. delegate void MethodInvoker();
  4. static void Foo()
  5. {
  6. int intAvailableThreads, intAvailableIoAsynThreds;
  7. ThreadPool.GetAvailableThreads(out intAvailableThreads,
  8. out intAvailableIoAsynThreds);
  9. string strMessage =
  10. String.Format(@"Is Thread Pool: {0},
  11. Thread Id: {1} Free Threads {2}",
  12. Thread.CurrentThread.IsThreadPoolThread.ToString(),
  13. Thread.CurrentThread.GetHashCode(),
  14. intAvailableThreads);
  15. Console.WriteLine(strMessage);
  16. Thread.Sleep(10000);
  17. return;
  18. }
  19. static void CallFoo()
  20. {
  21. MethodInvoker simpleDelegate =
  22. new MethodInvoker(Foo);
  23. for (int i = 0; i < 15; i++)
  24. {
  25. simpleDelegate.BeginInvoke(null, null);
  26. }
  27. }
  28. static void Main(string[] args)
  29. {
  30. ThreadPool.SetMaxThreads(10, 10);
  31. CallFoo();
  32. Console.ReadLine();
  33. }
  34. }

这个程序在起始的时候将线程池中最大线程个数设置为10个,然后做15次异步调用,每个异步调用中都停留10秒钟当作处理本身所要消耗的时间。从程序的执行我们可以看到,当前10个异步调用完全开始以后,新的异步调用就会等待(注意:不是主线程在等待),直到线程池中有线程空闲出来。

参考资料:异步方法调用

C#基础:异步调用 【转】的更多相关文章

  1. Direct3D Draw函数 异步调用原理解析

    概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...

  2. .Net组件程序设计之异步调用

    .Net组件程序设计之异步调用 说到异步调用,在脑海中首先想到就是BeginInvoke(),在一些常用对象中我们也会常常见到Invoke()和BeginInvoke(), 要想让自己的组件可以被客户 ...

  3. 谈谈RPC中的异步调用设计

    RPC(远过程调用)在分布式系统中是很常用的基础通讯手段,核心思想是将不同进程之间的通讯抽象为函数调用,基本的过程是调用端通过将参数序列化到流中并发送给服务端,服务端从流中反序列化出参数并完成实际的处 ...

  4. 似是而非的JS - 异步调用可以转化为同步调用吗?

    源起 小飞是一名刚入行前端不久的新人,因为进到了某个大公司,俨然成为了学弟学妹眼中'大神',大家遇到js问题都喜欢问他,这不,此时他的qq弹出了这样一条消息 "hi,大神在吗?我有个问题想问 ...

  5. C#中异步调用示例与详解

    using System; using System.Collections.Generic; using System.Text; using System.Runtime.InteropServi ...

  6. 使用Promise解决多层异步调用的简单学习【转】

    前言 本文章转载文章: https://www.jianshu.com/p/29da9aef4c1c 第一次接触到Promise这个东西,是2012年微软发布Windows8操作系统后抱着作死好奇的心 ...

  7. HttpClient异步调用引发的程序挂起问题排查及解决

    在搭建搭建分布式系统时,基础组件与框架的重要性不言而喻.但是如果组件出现bug,真的很要命.虽然我们通过各种单元测试,拼命找bug,但是总有一些问题被盲目自信蒙蔽了双眼,很多时候我们认为这段代码100 ...

  8. C#(同步调用、异步调用、异步回调)

    Review: 原作者虽然使用了汉字的类名,看起来十分蹩脚,但是,还是把同步调用.异步调用.异步回调的使用讲解的很详细的.原理讲解的很清晰. ------ 本文将主要通过“同步调用”.“异步调用”.“ ...

  9. .NET中的async和await关键字使用及Task异步调用实例

    其实早在.NET 4.5的时候M$就在.NET中引入了async和await关键字(VB为Async和Await)来简化异步调用的编程模式.我也早就体验过了,现在写一篇日志来记录一下顺便凑日志数量(以 ...

随机推荐

  1. windows多线程没那么难

    windows多线程没那么难 作者:vpoet mail:vpoet_sir@163.com 上一博文中我们引入了CreateThread()多线程编程一个简单的例子,事实上我说windows 多线程 ...

  2. error LNK2001

    error LNK2001: 无法解析的外部符号 "public: virtual void __cdecl Observer::update(float,float,float)" ...

  3. Clock network

    https://en.wikipedia.org/wiki/Clock_network

  4. zb的生日(暴搜dfs)

    zb的生日 时间限制:3000 ms  |  内存限制:65535 KB 难度:2   描述 今天是阴历七月初五,acm队员zb的生日.zb正在和C小加.never在武汉集训.他想给这两位兄弟买点什么 ...

  5. mysql的常用操作的封装

    1.概述: 为了把繁琐的操作简化成简单的类,设计了2个类用来封装了mysql的常用操作,以便使用者可以方便地使用. 2.组成 1)数据库操作类CDatabaseConnect 2)SQL对象类CSql ...

  6. iOS 苹果app提交 ITC.apps.validation.prerelease_build_missing

    提示这信息,由于没有选择包文件, 在提交页面以下bulid的区域, 加入你的app就可以 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWxpbmNleG ...

  7. 76 bytes for faster jQuery

    转载自http://james.padolsey.com/javascript/76-bytes-for-faster-jquery/ 作者JAMES PADOLSEY 在我们平时使用JQuery,调 ...

  8. AjaxHelper的get和post请求的封装类

    最近在学习ajax的时候发现不断的调用get和post请求时,代码重复很多,有的公司会用自带的封装的方法,这个可以直接调用ajax的方法,但是在运用的时候我们也应该学习它是怎么封装的和一些原理性的东西 ...

  9. 天坑 之 JSP编译错误

    情况:今天写自己的小网站,使用jsp+servlet+mysql,bean,dbutil,DAO等都已经写完,mySQL也已经创建好数据库,表,和字段,添加完成数据,启动tomcat,结果出现下图错误 ...

  10. Struct和Class的区别

    转载至:http://blog.csdn.net/yuliu0552/article/details/6717915 C++中的struct对C中的struct进行了扩充,它已经不再只是一个包含不同数 ...