zoukankan      html  css  js  c++  java
  • 第五讲:异步操作

    代码

    https://yunpan.cn/cPns5DkGnRGNs   密码:3913


    我们接着上一张的内容去讲,上一张的最后,看到了我们的程序报错了。 

    这里我们解释一下为什么报错,为什么会出现死锁呢?

    异常:究其本质,这是一个死锁导致的异常,由于默认的情况是服务按Single并发模式执行(在服务器执行过程中,服务对象只能被一个线程访问。WCF通过加锁的机制保证服务对象的独占性使用,也就是说在服务执行开始时会对服务对象加锁,该锁在服务操作结束之后释放)

    在Add操作执行的过程中,服务端会回调客户端操作进行运算结果的显示工作。

    如果回调采用单向操作:

    回调请求一经发出便会立即返回,不需要等待服务端的反馈,操作可以继续得到执行,直到操作正常结束。而另一方面的服务端,当调用操作在客户端正常执行后,需要反馈回到服务端所以试图访问操作的时候,发现对象被服务操作执行的线程锁定,所以他会等待服务操作执行完成后将锁释放。这样,服务操作需要等待回调操作进行正常的返回以便执行后续操作,而回调操作只有等待服务操作执行完成之后将锁释放才可以访问。所以就造成了死锁状态。

    通俗讲就是  因为客户端第一次调用服务端的Add方法的线程正在执行并且执行到了等待客户端的反馈信息,就这样一直的等待,资源不会被释放以至于处于锁定状态,而客户端也执行完成了相关操作,需要进行反馈服务端信息,但是服务端一直处于锁定状态,而无法再次请求。两边就产生了 矛盾。所以死锁了。


    那么怎么解决上面的问题呢?

    使用多线程或者异步操作

    多线程与异步操作

    按照操作执行所需要的资源类型,我们可以将操作分为CPU绑定型操作和I/O操作。对于前者,操作的执行主要利用CPU进行密集的计算:而对于后者,大部分的操作处理时间花在I/O操作处理,比如访问数据库,文件系统,网络资源等。对于I/O的操作,我们可以充分利用多线程的机制,让多个操作在自己的线程并发执行,从而提高系统性能和响应能力。服务调用就是典型的I/O操作,所以多线程在服务调用中具有广泛的应用。

    如果按照异步操作发生的位置,可以将WCF应用的异步操作分为下面2种情况:

    1:异步信道调用:

      客户端通过绑定创建的信道向服务端发送消息,从而实现了对服务的调用。客户端也可以通过代理对象异步地调用信道,从而实现异步服务调用。

    2:单向(One-Way)消息交换   (这一种我们第四讲已经说过了,只是没有点透,这里说单向消息交换   与 第一种  异步信道  其实是  异曲同工 的效果   )

      客户端信道通过单向的消息交换模式向服务端发送消息,消息一旦抵达传输层马上返回,从而达到异步服务调用的效果。

    我们重点说说  异步信道的调用:

    为了方便客户端进行异步的服务调用,最简单的方式是通过添加服务引用的方式来创建(第一讲我们说过)。通过该方式来创建异步服务代理,创建的方式只需要在添加服务引用对话框中点击“高级”按钮,便会填出一个“服务引用设置”对话框,勾选“生成异步操作”复选框即可。

    [ 5-01 ]

    [ 5-02 ]

    不管 勾不勾选 " 允许生成异步操作 " 的复选框   都会生成一个继承自ClientBase<TChannel>的类,但 勾选后 所不同的是,该类中会多出一些与异步服务调用相关的成员。 在具体通过服务代理进行异步服务调用的时候,可以采用不同的调用方式,不仅可以采用参数典型的BeginXxx和EndXxx的形式,也可以采用回调的形式,还可以采用事件注册的方式。

    例如:我们的契约中有个Add的方法,如果勾选了 " 允许生成异步操作 "  的复选框  就会多出一个  BeginAdd  和   EndAdd  的方法,这两个方法就是异步操作的方法。


    我们做一个异步服务调用的小Demo,代码还是在 云盘,自己去找:

    说明一下,异步服务调用 的发起者 一定是 客户端,所以  其它地方 不需要额外说明,与之前一样不变的代码

    [ 5-03 ]

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 
     7 
     8 namespace Client
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14 
    15 
    16             //必须先通过 先  添加服务引用
    17 
    18             //通过BeginXxx/EndXxx进行异步服务调用
    19             //在调用BeginAdd方法后,可以做一些额外的处理工作,这些工作将会和Add服务操作的调用并发地运行,最终的运算结果通过EndAdd方法得到
    20 
    21             //创建 服务引用的 代理
    22             ServiceCalculator.CalculatorClient proxy = new Client.ServiceCalculator.CalculatorClient();
    23             //异步操作
    24             IAsyncResult asynResult = proxy.BeginAdd(1, 2, null, null);
    25             //得到返回的结果
    26             double result = proxy.EndAdd(asynResult);
    27             proxy.Close();
    28             Console.WriteLine("x+y={2} when x={0} and y={1}", 1, 2, result);
    29             Console.Read();
    30 
    31 
    32             /*
    33              其实上面的方法并不好
    34              是当EndAdd方法被执行的时候 如果异步执行的Add方法(也就是调用服务端的方法)还没有执行结束的话,在EndAdd方法这里将会阻塞当前线程(这个当前的线程指的是 客户端的线程)
    35              并等待异步方法(调用服务端的Add方法)的结束,这样往往不能起到多线程并发执行应有的作用。我么真正希望的是在异步执行结束后自动回调设定的操作,这样就可以采用回调的方式
    36              来实现这样的机制。
    37              
    38              去看第二种异步调用的方法
    39              
    40              */
    41 
    42 
    43         }
    44     }
    45 }

    上面的是第一种 异步 服务调用,但是不完善。有朋友问 不完善写这个弄啥嘞,这里只是让不太理解异步调用的朋友加深印象。

    [ 5-04 ]

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 
     7 
     8 namespace Client
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             //通过回调的方式进行异步服务调用
    15             //在异步执行结束后自动回调设定的操作,这样就可以采用回调的方式来实现这样的机制。
    16 
    17             //创建 服务引用的 代理
    18             ServiceCalculator.CalculatorClient proxy = new Client.ServiceCalculator.CalculatorClient();
    19             //执行Add的异步方法BeginAdd   1,2为参数,异步执行结束后自动调用的委托方法,向委托传入对象
    20             proxy.BeginAdd(1, 2, delegate(IAsyncResult asyncResult)
    21             {
    22                 //这里得到的就是 向委托传入的对象  也就是 proxy.BeginAdd 的第四个参数 new double[] { 1, 2 }
    23                 double[] operands = asyncResult.AsyncState as double[];
    24                 //得到返回的结果
    25                 double result = proxy.EndAdd(asyncResult);
    26                 //关闭代理对象
    27                 proxy.Close();
    28                 Console.WriteLine("x+y={2} when x={0} and y={1}", operands[0], operands[1], result);
    29             }, new double[] { 1, 2 });
    30             Console.Read();
    31         }
    32     }
    33 }

    上面这种就真正的 能达到  异步线程的调用。  这种方式 是比较提倡使用的方式。

    再说最后一种 异步服务调用的 方式:

    [ 5-05 ]

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.ServiceModel;
     6 
     7 
     8 namespace Client
     9 {
    10     class Program
    11     {
    12         static void Main(string[] args)
    13         {
    14             //通过事件注册的方式进行异步服务调用
    15             //实际上,事件注册和通过回调从表现上看比较类似。
    16 
    17             //创建 服务引用的 代理
    18             ServiceCalculator.CalculatorClient proxy = new Client.ServiceCalculator.CalculatorClient();
    19 
    20             //异步执行结束后自动调用的事件( 也就是在 下面的 代码  proxy.AddAsync(1, 2, new double[] { 1, 2 })  完成之后 调用的事件  )
    21             //第一个参数为他本身,第二个参数为它整个事件的数据源
    22             proxy.AddCompleted += delegate(object sender, Client.ServiceCalculator.AddCompletedEventArgs argss)
    23             {
    24                 //获取 向事件传入的对象  也就是 proxy.AddAsync 的第三个参数 new double[] { 1, 2 }
    25                 double[] operands = argss.UserState as double[];
    26                 //得到返回的结果
    27                 double result = argss.Result;
    28                 //关闭代理对象
    29                 proxy.Close();
    30                 Console.WriteLine("x+y={2} when x={0} and y={1}", operands[0], operands[1], result);
    31             };
    32             proxy.AddAsync(1, 2, new double[] { 1, 2 });
    33             Console.Read();
    34         }
    35     }
    36 }

    这种方式 其实  跟 第二种 方式  大差不差,所以 也是一个推介的 写法,至于 用那一种,大家自己看吧。第一种是不推介使用的。

    到这里 就把  服务契约就说完了。


    说到这里,我们不得不提一下 异步的 好坏

    异步的优缺点及其应用场合

    异步并不一定能提高系统性能,甚至因为线程的创建,消亡,和切换会增加系统开销,但异步除了提高性能,还可以增强系统的健壮性。在过去,windows程序总是单线程的,在这样的系统中,如果出现了异常,系统就会 因此而崩溃,甚至连我们的操作系统也是单线程的,所以每次出现异常,我们的计算机用户都要不厌其烦强制关机,然后重启才能解决问题。加入多线程之后,当一个线程上的任务发生异常的时候,其他线程有能力不受影响,从此防止整个应用程序的崩溃。此外如果用户是在一个UI中操作某项耗时的操作,如果不使用异步,那UI线程就会被阻塞,导致界面无法响应,用户就会很无助,增加了异步,让复杂的任务在另外的线程中完成,就会有比较好的用户体验。而且异步并不是说对性能提高没有作用,CLR线程的创建,销毁,和线程上下文切换的确会有很大的开销,比如每创建一个线程,都必须申请1MB的地址空间用于线程的用户模式,申请12KB左右的地址空间用于线程的内核模式,而且还要求进程调用每个dll中的一个 固定的函数来通知所有的dll系统创建了一个新的线程,同样在销毁的时候,也要做类似的通知,上面这一切似乎都说明了异步操作对于性能的坏处,但事实并非完全如此,我们知道当前的处理器基本上都是双核,或者支持hyper-thread,一个线程的执行总会占用1个cpu逻辑核,如果我们的计算机是4核,8核,而我们不采用异步,那其实多核就没什么太大优势,因为总是1个核在工作,而另外的核却在休息,效率肯定低下,而此时用多线程,就可以充分使用计算机的处理器资源。同时对于一些有IO限制的操作而言,如读取磁盘文件,网络数据相关操作时,整个过程并不是完全靠运算,而是要通过磁盘驱动器或者网络驱动器来协助完成,比如读取磁盘中的一个文件,当应用程序的读取线程发出读取请求的时候,该请求会被磁盘驱动器所排队处理,假如它是个很长的操作,那么该操作会在磁盘驱动器上排队或者执行很长时间,而这段时间读线程就处于阻塞的状态,这样就浪费了线程资源,正确的做法应该是线程将读请求发送到磁盘驱动器后马上返回,继续处理其他任务,而当磁盘驱动器操作完成的时候,由磁盘驱动器来通知或者由一个线程来轮询执行状态。这样就防止线程资源被浪费,从而提高系统性能。
     
    总结一下上面的说法,异步有三个优点: 

    1) 在I/O受限等情况下,异步能提高性能,并且能更加充分利用多核CPU的优点。
    2) 异步能增强系统健壮性
    3) 异步能改善用户体验 

    同时也有缺点,如下坏处:

    1) 滥用异步,会影响性能
    2) 增加编程难度 

  • 相关阅读:
    钱多多软件制作04
    团队项目01应用场景
    HDU 4411 arrest
    HDU 4406 GPA
    HDU 3315 My Brute
    HDU 3667 Transportation
    HDU 2676 Matrix
    欧拉回路三水题 POJ 1041 POJ 2230 POJ 1386
    SPOJ 371 BOXES
    POJ 3422 Kaka's Matrix Travels
  • 原文地址:https://www.cnblogs.com/xulang/p/5489297.html
Copyright © 2011-2022 走看看