继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

C#基础:异步调用

慕工程3455409
关注TA
已关注
手记 318
粉丝 78
获赞 293

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

  1. 小明在烧水,等水烧开以后,将开水灌入热水瓶,然后开始整理家务

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

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

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

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

view plaincopy to clipboardprint?

  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.   

  11.     delegate TimeSpan BoilingDelegate();   

  12.   

  13.     static void Main(string[] args)   

  14.     {   

  15.         Console.WriteLine("小文:将水壶放在炉子上");   

  16.         BoilingDelegate d = new BoilingDelegate(Boil);   

  17.         IAsyncResult result = d.BeginInvoke(BoilingFinishedCallback, null);   

  18.         Console.WriteLine("小文:开始整理家务...");   

  19.         for (int i = 0; i < 20; i++)   

  20.         {   

  21.             Console.WriteLine("小文:整理第{0}项家务...", i + 1);   

  22.             Thread.Sleep(1000);   

  23.         }   

  24.     }   

  25.   

  26.     static void BoilingFinishedCallback(IAsyncResult result)   

  27.     {   

  28.         AsyncResult asyncResult = (AsyncResult)result;   

  29.         BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;

  30.         del.EndInvoke(result);

  31.         Console.WriteLine("小文:将热水灌到热水瓶");   

  32.         Console.WriteLine("小文:继续整理家务");   

  33.            

  34.     }   

  35. }  

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

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

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

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

view plaincopy to clipboardprint?

  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改成下面的形式:

view plaincopy to clipboardprint?

  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方法中,为了得到委托实例以便获取异步处理函数的返回值,我们采用了下面的转换:

view plaincopy to clipboardprint?

  1. AsyncResult asyncResult = (AsyncResult)result;   

  2. BoilingDelegate del = (BoilingDelegate)asyncResult.AsyncDelegate;   

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

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

  • 异步函数由线程完成,这个线程是.NET线程池中的线程

  • 通常情况下,.NET线程池拥有500个线程(当然这个数量可以设置),每当调用BeginInvoke开始异步处理时,异步处理函数就由线程池中的某个线程负责执行,而用户无法控制具体是由哪个线程负责执行

  • 由于线程池中线程数量有限,因此当池中线程被完全占用时,新的调用请求将使函数不得不等待空余线程的出现。此时,程序的效率会有所影响

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

view plaincopy to clipboardprint?

  1. class Program   

  2. {   

  3.     delegate void MethodInvoker();   

  4.   

  5.     static void Foo()   

  6.     {   

  7.         int intAvailableThreads, intAvailableIoAsynThreds;   

  8.   

  9.         ThreadPool.GetAvailableThreads(out intAvailableThreads,   

  10.                 out intAvailableIoAsynThreds);   

  11.   

  12.         string strMessage =   

  13.             String.Format(@"Is Thread Pool: {0},   

  14.         Thread Id: {1} Free Threads {2}",   

  15.                 Thread.CurrentThread.IsThreadPoolThread.ToString(),   

  16.                 Thread.CurrentThread.GetHashCode(),   

  17.                 intAvailableThreads);   

  18.   

  19.         Console.WriteLine(strMessage);   

  20.   

  21.         Thread.Sleep(10000);   

  22.   

  23.         return;   

  24.     }   

  25.   

  26.     static void CallFoo()   

  27.     {   

  28.         MethodInvoker simpleDelegate =   

  29.             new MethodInvoker(Foo);   

  30.   

  31.         for (int i = 0; i < 15; i++)   

  32.         {   

  33.             simpleDelegate.BeginInvoke(nullnull);   

  34.         }   

  35.     }   

  36.   

  37.     static void Main(string[] args)   

  38.     {   

  39.         ThreadPool.SetMaxThreads(10, 10);   

  40.         CallFoo();   

  41.         Console.ReadLine();   

  42.     }   

  43. }   

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

参考资料:异步方法调用

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP