上面的程序算是"手工"异步调用。程序员控制线程的生成和多线程的同步问题。熟悉Java多线程编程的朋友会感觉非常的熟悉。但事实上,Microsoft并不鼓励你这样来写程序。因为他们认为多线程编程比较复杂而且容易出错,并且你的线程使用方法往往不够标准和优化。微软认为线程的生成和管理对一个程序的性能和质量是非常重要的,越复杂的程序就越明显。因此Microsoft创建了一整套线程生成和管理的服务,并鼓励你在此基础之上开发你的应用程序。其技术核心就是我前面提到的“秘书处(Thread pool 线程池)”。.NET替你管理这个"秘书处",它根据程序运行时的软硬件资源情况来决定"雇佣"多少"秘书"为最优。当你的程序被放置在多CPU的高档服务器上运行的时候,.NET会自动调整线程数量以最大限度的提升程序性能。程序员在普通工作站开发编程时将不必考虑这些问题。
使用.NET提供的异步调用服务
当你在Visual Studio中加一个Web引用(Reference)的时候,.NET在后台给你生成了一个代理类。令人惊讶的是异步调用的函数也自动被生成了。就前面给的Web Service而言,这个自动生成的代理类为。
程序中的黑体部分就是关于异步调用函数的程序段。你调用BegingetStockPrice()后将返回一个具有IAsyncResult界面的对象。通过它你就可以最终拿到函数的运行结果。Microsoft提供的异步调用比较灵活,下面就几种常见的用法做个介绍。
1.使用Pooling方法得到返回结果
你调用BegingetStockPrice()后得到一个具有IAsyncResult界面的对象。它提供了IsCompleted的属性。当这个值为"True"的时候,你就可以通过调用EndgetStockPrice()来拿到函数运行的结果。
更重要的是,你可以在异步调用的时候"放弃Abort"调用。下面的程序段是用3个"按钮(Button)"来示意如何使用异步调用,检查调用是否结束以及放弃调用。
2.使用WaitHandle
你调用BegingetStockPrice()后得到一个具有IAsyncResult界面的对象。它提供了AsyncWaitHandle的属性。调用它的WaitOne()函数可以使程序被阻拦直到另外一个线程函数调用完成。之后程序将继续往下执行。
从现象上看,和同步调用相比你并没有得到好处。程序等待的时候仍然处于"挂起"状态。但是在有些情况下,"等待"还是有它的特色的。比如说你可以连续调用三个WebMethod,如果每个函数费时5秒,那么使用同步的话总共会使用15秒钟。但如果使用异步的话,你可能只要等待5秒钟。当然这要使用WaitHandle提供的WaitAll()函数。如下所示:
3.使用回调函数(CallBack)
看到现在,你可能还没有感到满意。因为你异步调用了函数后,还要手工检查函数是否执行完毕,或者要处于等待状态。能否让函数完成后,自动显示结果或是做其它操作呢?答案是"能"的。回调函数就是做这种事情的。
还记得我们前面说的例子吗。"王秘书,给我打壶开水去。打回来后给我沏杯茶()"。王秘书打水完成后,还会执行沏杯茶()这个回调函数。如果你原意,你可以让王秘书干其它任何事情。如下所示:
如果你喝茶比较讲究,你可能会说,"王秘书,给我打壶开水去()。打回来后给我沏杯茶(),要龙井茶"。那么这个"龙井茶"是否可以传给王秘书呢?这样她在打回开水后沏茶的时候就知道用什么茶叶了。答案是可以的。如上面的例程所示,DateTime.Now在异步调用的时候被传递给了新的线程。在回调函数里面,这个参数可以拿出来使用。handle.AsyncState.就是这个被传进的参数。.NET规定这个参数可以是任意一个对象。所以如果有多个参数要传递的话,可以使用数组,HashTable等等。理论上讲,你可以传递任何东西。
线程间同步协调问题
从上面的例子你可以看出,异步调用的本质是由另外一个线程在真正执行函数的调用,不管是你自己直接生成的线程还是.NET提供的。当一旦进入多线程编程后,最重要的问题就是线程间的协调和同步。要高度注意的是线程间的问题往往很隐蔽,很难发现。在单处理器平台上也许不会出现问题,但移植到多处理器平台上后也许就会显现出来。.NET提供了许多方法来进行多线程的保护和协调。限于篇幅,这里不一一赘诉了。
异步的WebService
上文讨论的是如何在客户端异步的调用服务器端的WebService。但是客户端的努力还终究是有一些局限的。比如在客户端调用一个费时的WebService时,你很可能希望客户端有个"状态条"来不断的指示函数调用的进度,并且还可以随时"放弃"调用。这个任务单凭客户端程序就很难就决。这种情形就要求服务器端能提供异步服务了。任何开发异步的WebService是一个比较复杂的问题,笔者将在后文中再作介绍。谢谢!
当你在Visual Studio中加一个Web引用(Reference)的时候,.NET在后台给你生成了一个代理类。令人惊讶的是异步调用的函数也自动被生成了。就前面给的Web Service而言,这个自动生成的代理类为。
//----------------------------------------------------------------------- // <autogenerated> // This code was generated by a tool. // Runtime Version: 1.0.3705.209 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // </autogenerated> //----------------------------------------------------------------------- // // This source code was auto-generated by Microsoft.VSDesigner, Version // 1.0.3705.209. // namespace AsyncClient_01.localhost { using System.Diagnostics; using System.Xml.Serialization; using System; using System.Web.Services.Protocols; using System.ComponentModel; using System.Web.Services; /// <remarks/> [System.Diagnostics.DebuggerStepThroughAttribute()] [System.ComponentModel.DesignerCategoryAttribute("code")] [System.Web.Services.WebServiceBindingAttribute( Name="StockPriceSoap", Namespace="http://tempuri.org/")] public class StockPrice : System.Web.Services.Protocols. SoapHttpClientProtocol { /// <remarks/> public StockPrice() { this.Url = "http://localhost/StockService/StockService.asmx"; } /// <remarks/> [System.Web.Services.Protocols.SoapDocumentMethodAttribute ("http://tempuri.org/getStockPrice", RequestNamespace="http://tempuri.org/", ResponseNamespace="http://tempuri.org/", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle= System.Web.Services.Protocols.SoapParameterStyle.Wrapped)] public System.Double getStockPrice(string stockSymbol) { object[] results = this.Invoke("getStockPrice", new object[] { stockSymbol}); return ((System.Double)(results[0])); } /// <remarks/> public System.IAsyncResult BegingetStockPrice (string stockSymbol, System.AsyncCallback callback, object asyncState) { return this.BeginInvoke("getStockPrice", new object[] { stockSymbol}, callback, asyncState); } /// <remarks/> public System.Double EndgetStockPrice(System.IAsyncResult asyncResult) { object[] results = this.EndInvoke(asyncResult); return ((System.Double)(results[0])); } } } |
程序中的黑体部分就是关于异步调用函数的程序段。你调用BegingetStockPrice()后将返回一个具有IAsyncResult界面的对象。通过它你就可以最终拿到函数的运行结果。Microsoft提供的异步调用比较灵活,下面就几种常见的用法做个介绍。
1.使用Pooling方法得到返回结果
你调用BegingetStockPrice()后得到一个具有IAsyncResult界面的对象。它提供了IsCompleted的属性。当这个值为"True"的时候,你就可以通过调用EndgetStockPrice()来拿到函数运行的结果。
更重要的是,你可以在异步调用的时候"放弃Abort"调用。下面的程序段是用3个"按钮(Button)"来示意如何使用异步调用,检查调用是否结束以及放弃调用。
//客户端的WebService引用 private localhost.StockPrice m_stockService; private IAsyncResult m_handle; //异步调用WebMethod private void button2_Click(object sender, System.EventArgs e) { m_stockService = new localhost.StockPrice(); m_handle = m_stockService.BegingetStockPrice("IBM",null,null); messageBox.Text += "Function is invoked." + System.Environment.NewLine; } //检查异步调用是否完成。如果完成的话,就取出调用结果 private void button3_Click(object sender, System.EventArgs e) { if(m_handle==null) { MessageBox.Show(this, "No function is called!"); return; } if(m_handle.IsCompleted == false) messageBox.Text += "Price is not ready yet" + System.Environment.NewLine; else { double price = m_stockService.EndgetStockPrice(m_handle); messageBox.Text += "The Price is: " + price + System.Environment.NewLine; } } //放弃异步调用 private void button4_Click(object sender, System.EventArgs e) { if(m_handle!=null) { WebClientAsyncResult result = (WebClientAsyncResult)m_handle; result.Abort(); m_handle = null; } messageBox.Text += "Function call is aborted!" + System.Environment.NewLine; } |
2.使用WaitHandle
你调用BegingetStockPrice()后得到一个具有IAsyncResult界面的对象。它提供了AsyncWaitHandle的属性。调用它的WaitOne()函数可以使程序被阻拦直到另外一个线程函数调用完成。之后程序将继续往下执行。
private void button8_Click(object sender, System.EventArgs e) { if(this.m_stockService ==null) m_stockService = new localhost.StockPrice(); m_handle = m_stockService.BegingetStockPrice("IBM",null,null); messageBox.Text = "The function is called" + System.Environment.NewLine; m_handle.AsyncWaitHandle.WaitOne(); double price = m_stockService.EndgetStockPrice(m_handle); messageBox.Text += "The price is: " + price + system.Environment.NewLine; } |
从现象上看,和同步调用相比你并没有得到好处。程序等待的时候仍然处于"挂起"状态。但是在有些情况下,"等待"还是有它的特色的。比如说你可以连续调用三个WebMethod,如果每个函数费时5秒,那么使用同步的话总共会使用15秒钟。但如果使用异步的话,你可能只要等待5秒钟。当然这要使用WaitHandle提供的WaitAll()函数。如下所示:
private void button7_Click(object sender, System.EventArgs e) { if(this.m_stockService ==null) m_stockService = new localhost.StockPrice(); IAsyncResult[] handles = new IAsyncResult[3]; for(int i=0;i<3;i++) handles[i] = m_stockService.BegingetStockPrice("IBM",null,null); messageBox.Text = "3 function is called" + System.Environment.NewLine; WaitHandle[] WaitHandles = {handles[0].AsyncWaitHandle, handles[1].AsyncWaitHandle, handles[2].AsyncWaitHandle}; //函数被阻拦,直到3个函数都执行完毕。WaitAny()函数情况类似,但有一个函数完成后 //程序就解阻,继续往下执行 WaitHandle.WaitAll(WaitHandles); double[] prices = new double[3]; for(int i=0;i<3;i++) { prices[i] = m_stockService.EndgetStockPrice(handles[i]); messageBox.Text += "The price is: " + prices[i] + System.Environment.NewLine; } } |
3.使用回调函数(CallBack)
看到现在,你可能还没有感到满意。因为你异步调用了函数后,还要手工检查函数是否执行完毕,或者要处于等待状态。能否让函数完成后,自动显示结果或是做其它操作呢?答案是"能"的。回调函数就是做这种事情的。
还记得我们前面说的例子吗。"王秘书,给我打壶开水去。打回来后给我沏杯茶()"。王秘书打水完成后,还会执行沏杯茶()这个回调函数。如果你原意,你可以让王秘书干其它任何事情。如下所示:
private void button5_Click(object sender, System.EventArgs e) { if(this.m_stockService ==null) m_stockService = new localhost.StockPrice(); //生成回调函数 AsyncCallback cb = new AsyncCallback (this.callback); m_stockService.BegingetStockPrice("IBM",cb,DateTime.Now); messageBox.Text = "The function is called" + System.Environment.NewLine; } private void callback(IAsyncResult handle) { double price = m_stockService.EndgetStockPrice(handle); lock(this) { messageBox.Text += "The price is: " + price + ". Reques time: " + handle.AsyncState.ToString() + ", and returned at: " + DateTime.Now.ToString() + System.Environment.NewLine; } } |
如果你喝茶比较讲究,你可能会说,"王秘书,给我打壶开水去()。打回来后给我沏杯茶(),要龙井茶"。那么这个"龙井茶"是否可以传给王秘书呢?这样她在打回开水后沏茶的时候就知道用什么茶叶了。答案是可以的。如上面的例程所示,DateTime.Now在异步调用的时候被传递给了新的线程。在回调函数里面,这个参数可以拿出来使用。handle.AsyncState.就是这个被传进的参数。.NET规定这个参数可以是任意一个对象。所以如果有多个参数要传递的话,可以使用数组,HashTable等等。理论上讲,你可以传递任何东西。
从上面的例子你可以看出,异步调用的本质是由另外一个线程在真正执行函数的调用,不管是你自己直接生成的线程还是.NET提供的。当一旦进入多线程编程后,最重要的问题就是线程间的协调和同步。要高度注意的是线程间的问题往往很隐蔽,很难发现。在单处理器平台上也许不会出现问题,但移植到多处理器平台上后也许就会显现出来。.NET提供了许多方法来进行多线程的保护和协调。限于篇幅,这里不一一赘诉了。
上文讨论的是如何在客户端异步的调用服务器端的WebService。但是客户端的努力还终究是有一些局限的。比如在客户端调用一个费时的WebService时,你很可能希望客户端有个"状态条"来不断的指示函数调用的进度,并且还可以随时"放弃"调用。这个任务单凭客户端程序就很难就决。这种情形就要求服务器端能提供异步服务了。任何开发异步的WebService是一个比较复杂的问题,笔者将在后文中再作介绍。谢谢!