第五讲:异步操作
代码
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 }
这种方式 其实 跟 第二种 方式 大差不差,所以 也是一个推介的 写法,至于 用那一种,大家自己看吧。第一种是不推介使用的。
到这里 就把 服务契约就说完了。
说到这里,我们不得不提一下 异步的 好坏
异步的优缺点及其应用场合
1) 在I/O受限等情况下,异步能提高性能,并且能更加充分利用多核CPU的优点。
2)
异步能增强系统健壮性
3)
异步能改善用户体验
同时也有缺点,如下坏处:
1) 滥用异步,会影响性能
2) 增加编程难度