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

  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. 手动调整导航控制器中的viewcontroller

    NSMutableArray *array = [[NSMutableArray alloc]initWithArray: [self.navigationController viewControl ...

  2. Easy Number Challenge(暴力,求因子个数)

    Easy Number Challenge Time Limit:2000MS     Memory Limit:262144KB     64bit IO Format:%I64d & %I ...

  3. 技术之美[程序人生]我在IBM实习的日子

    写这篇文章的时候,我已经在IBM正式工作了,看看上一篇博文的发布日期,才发现,我已经将近三个月没有更新博客了,多么惊人!为什么这么久?期间发生了很多事情.最重要的一件就是我大学毕业了!毕业的那么平淡, ...

  4. 使用CSC.EXE编译第一个HELLO WORLD

    坐的没事,下了个C#2008,看帮助文件写了个HELLO.CS的源文件: //hello.cs //Show "Hello Word!" using system; class h ...

  5. CSS圆角样式

    CSS圆角: /*纯css,设置图片圆角*/ #top2 { margin-left:20px; padding:10px; width:600px; height:300px; border: 5p ...

  6. 【Lucene4.8教程之一】使用Lucene4.8进行索引及搜索的基本操作

    在Lucene对文本进行处理的过程中,可以大致分为三大部分: 1.索引文件:提取文档内容并分析,生成索引 2.搜索内容:搜索索引内容,根据搜索关键字得出搜索结果 3.分析内容:对搜索词汇进行分析,生成 ...

  7. HTML5的结构学习(2) --- 新增的非主体结构元素

    除了上一篇学习到的主体结构元素之外,html5还增加了一些表示逻辑结构和附加信息的非主体结构元素: 1.header 解释:一种具有引导和导航作用的结构元素. 用途:通常用来放置整个页面或者页面内某一 ...

  8. js分页代码

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xh ...

  9. wamp启动mysql的命令

    输入net start mysql提示服务名无效是因为在wamp集成安装中,mysql的服务名字是叫wampmysqld,可以用net start查看服务列表.所以命令启动mysql就应该输入net ...

  10. PHP文件缓存类

    <?php /** * @desc 文件缓存 */ class Cache{ const C_FILE = '/Runtime/'; private $dir = ''; const EXT = ...