WebService是微软.NET技术的一个亮点,它使得跨平台、跨语言、基于Internet上的互操作成为可能。Visual Studio.NET的IDE使得WebService的开发变得非常容易。程序员不需要直接面对SOAP, UDDI以及WSDL等繁琐的细节。但是由于WebService基于Internet的本质,使得对调用它的客户端程序提出了一些新的挑战。举例来说,调用WebService往往会经历较长的时延,或者是得不到响应(原因可能是Internet的连接问题,带宽问题,对方服务器过于繁忙或者是宕机等)。这样,如果客户端程序使用桌面程序中广泛采用的同步函数调用就会使得程序在等待返回结果时被“挂起”,不能响应用户的键盘和鼠标事件,用户甚至无法“放弃(Abort)”操作。这种情况对于用户来说是不可以接收的。在这种情况下,你可能要考虑使用异步方式来调用服务器端的WebMethod了。这样的话,你可以在发出调用后,不被挂起,而继续做其它事情。
异步调用的本质
有不少读者可能对"同步"和"异步"的概念以及回调函数等等术语还有些疑惑。这里就举个简单的例子说明一下。
比方说,你早上到了办公室,去打开水,打回水后然后沏茶。你同步调用打开水和沏茶两个函数。打开水完成后才可能沏茶。沏茶完成后你才可以看文件或是做其它工作。如果茶炉房出了些问题或者是人很多,那么你就要等着,直到打到了开水,你才可以回来干其它事情。在茶炉房等待开水的时候你什么也不能干。这种情况叫做被阻拦(Block)。准确的说就是执行函数的线程被阻拦。
要避免这种情况,你可以到达办公室后,说“王秘书,给我打壶开水去”。说完后你就可以开始看文件,做其他事了。等一会儿后,王秘书回来了,你就可以沏茶了。叫王秘书打开水就意味着你异步调用打开水这个函数。你不再担心在茶炉房等开水,你吩咐完王秘书后,你没有被阻拦,你可以马上开始做其它事情。但是开水总是要有个人去打,在这里王秘书是真正完成打开水函数的人。用计算机术语来将,王秘书可以是一个进程(Process)或是一个线程(Thread)。从理论上将,进程和线程都可以完成打开水这件事,但是就这类问题而言,使用进程是非常"昂贵"的,并且新的进程和原进程的通讯要复杂和慢的多,所以通常情况下,王秘书将会是一个线程。
如果你对王秘书说"王秘书,给我打开水。打回来后给我沏杯茶"。这种情况下,沏茶就是一个回调函数。沏茶将由王秘书这个"线程"来执行,在完成了打开水之后。
看起来很COOL是吧。有个"王秘书"使唤方便多了。但是别忘了王秘书出现后会有些其它问题。比如王秘书打回开水后给你沏茶时,你正用着茶几。她和你没有协调好撞在了一起,并把开水倒到了你的裤子上。因为办公室有和两个人,就出现了相互协调的问题。用计算机术语讲就是"多线程同步Multi-Thread Synchronization"。在计算机世界里,多个线程如何协调,同步是一个并不简单的问题。搞不好回造成信息紊乱,程序死锁。
让我们把这个问题想的再深一层。王秘书如果打开水的时候出了事打不回开水怎么办?你可能在开始的时候回对她说,"要是半个小时打不到水就算了"。这样就要求王秘书有时间概念。从计算机上讲,就是Timeout的机制。如果王秘书半个小时后没有回来,你可能想派刘秘书再去打。但是王秘书还拿着水壶呢(占着资源hold resources),这可怎么办?如果一切顺利,王秘书打完开水,沏完茶后该如何处理呢?是让她"走人"呢还是留着呢?因为你可能一会儿还会有拿报纸,寄邮件等等杂事。留着她也许还有用。可是王秘书会办其它事吗?如果不行的话,你可能还要用刘秘书,赵秘书等等来干每一项具体工作。另外公司里可能不止你一个"头头",孙总,李CEO,马董事长也需要人"侍候"。这时候成立一个"秘书处"可能比每人配备一堆秘书更经济更有效。因为使用秘书是要有代价的(线程的生成,释放,同步是要占用计算机CPU时间和内存等资源的)。这个"秘书处"在计算机里的对应物就是线程池(Thread Pool)。这是当今开发服务器端程序普遍采用的一个技术,目的是最大可能的提高程序性能,优化资源配置。
使用用户自定义"线程类(Thread)"
异步调用不仅在调用WebService的WebMethod时有用,在调用任何一个很费时的函数时,异步都是一个很好的选择。在.NET出现以前,我们可以生成新的线程,让这个线程来完成费时函数的调用,而主线程可以继续其它工作。比如在Java里,我们可以创建一个Thread类的子类或是创建一个实现了Runnable的类来完成这项工作。在.NET里,我们仍然可以这样做。如下面的小例程所示。
服务器端是一个非常简单的WebMethod,仅为示意:
客户端程序是一个C#写的简单的Windows Form程序。其的核心语句为:
有不少读者可能对"同步"和"异步"的概念以及回调函数等等术语还有些疑惑。这里就举个简单的例子说明一下。
比方说,你早上到了办公室,去打开水,打回水后然后沏茶。你同步调用打开水和沏茶两个函数。打开水完成后才可能沏茶。沏茶完成后你才可以看文件或是做其它工作。如果茶炉房出了些问题或者是人很多,那么你就要等着,直到打到了开水,你才可以回来干其它事情。在茶炉房等待开水的时候你什么也不能干。这种情况叫做被阻拦(Block)。准确的说就是执行函数的线程被阻拦。
要避免这种情况,你可以到达办公室后,说“王秘书,给我打壶开水去”。说完后你就可以开始看文件,做其他事了。等一会儿后,王秘书回来了,你就可以沏茶了。叫王秘书打开水就意味着你异步调用打开水这个函数。你不再担心在茶炉房等开水,你吩咐完王秘书后,你没有被阻拦,你可以马上开始做其它事情。但是开水总是要有个人去打,在这里王秘书是真正完成打开水函数的人。用计算机术语来将,王秘书可以是一个进程(Process)或是一个线程(Thread)。从理论上将,进程和线程都可以完成打开水这件事,但是就这类问题而言,使用进程是非常"昂贵"的,并且新的进程和原进程的通讯要复杂和慢的多,所以通常情况下,王秘书将会是一个线程。
如果你对王秘书说"王秘书,给我打开水。打回来后给我沏杯茶"。这种情况下,沏茶就是一个回调函数。沏茶将由王秘书这个"线程"来执行,在完成了打开水之后。
看起来很COOL是吧。有个"王秘书"使唤方便多了。但是别忘了王秘书出现后会有些其它问题。比如王秘书打回开水后给你沏茶时,你正用着茶几。她和你没有协调好撞在了一起,并把开水倒到了你的裤子上。因为办公室有和两个人,就出现了相互协调的问题。用计算机术语讲就是"多线程同步Multi-Thread Synchronization"。在计算机世界里,多个线程如何协调,同步是一个并不简单的问题。搞不好回造成信息紊乱,程序死锁。
让我们把这个问题想的再深一层。王秘书如果打开水的时候出了事打不回开水怎么办?你可能在开始的时候回对她说,"要是半个小时打不到水就算了"。这样就要求王秘书有时间概念。从计算机上讲,就是Timeout的机制。如果王秘书半个小时后没有回来,你可能想派刘秘书再去打。但是王秘书还拿着水壶呢(占着资源hold resources),这可怎么办?如果一切顺利,王秘书打完开水,沏完茶后该如何处理呢?是让她"走人"呢还是留着呢?因为你可能一会儿还会有拿报纸,寄邮件等等杂事。留着她也许还有用。可是王秘书会办其它事吗?如果不行的话,你可能还要用刘秘书,赵秘书等等来干每一项具体工作。另外公司里可能不止你一个"头头",孙总,李CEO,马董事长也需要人"侍候"。这时候成立一个"秘书处"可能比每人配备一堆秘书更经济更有效。因为使用秘书是要有代价的(线程的生成,释放,同步是要占用计算机CPU时间和内存等资源的)。这个"秘书处"在计算机里的对应物就是线程池(Thread Pool)。这是当今开发服务器端程序普遍采用的一个技术,目的是最大可能的提高程序性能,优化资源配置。
异步调用不仅在调用WebService的WebMethod时有用,在调用任何一个很费时的函数时,异步都是一个很好的选择。在.NET出现以前,我们可以生成新的线程,让这个线程来完成费时函数的调用,而主线程可以继续其它工作。比如在Java里,我们可以创建一个Thread类的子类或是创建一个实现了Runnable的类来完成这项工作。在.NET里,我们仍然可以这样做。如下面的小例程所示。
服务器端是一个非常简单的WebMethod,仅为示意:
//服务器端的程序 using System; using System.Collections; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Web; using System.Web.Services; using System.Threading; namespace StockService { public class StockPrice : System.Web.Services.WebService { public StockPrice() { InitializeComponent(); } #region Component Designer generated code //Required by the Web Services Designer private IContainer components = null; /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { } /// <summary> /// Clean up any resources being used. /// </summary> protected override void Dispose( bool disposing ) { if(disposing && components != null) { components.Dispose(); } base.Dispose(disposing); } #endregion [WebMethod] public double getStockPrice(String stockSymbol) { //sleep 5 seconds and return a dummy number Thread.Sleep(5000); return new Random().NextDouble() + 15.0; } } } |
客户端程序是一个C#写的简单的Windows Form程序。其的核心语句为:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.Threading; using System.Web.Services.Protocols; namespace AsyncClient_01 { public class Form1 : System.Windows.Forms.Form { private System.Windows.Forms.TextBox messageBox; private System.Windows.Forms.Button button1; //。。。 //省略了其它界面部分的程序 //。。。 private void button1_Click(object sender, System.EventArgs e) { //生成Mythread对象,由它去进行函数调用。同时注册事件响应函数 //用来显示异步调用结果 MyThread aThread = new MyThread("IBM"); aThread.gotPrice += new EventHandler(this.displayPrice); } private void displayPrice(object sender, System.EventArgs e) { GotPriceEvent ge = e as GotPriceEvent; //这段程序是由MyThread内产生的线程来执行的。为了避免和程序的主线程 //发生冲突,使用了LOCK机制。另外,股票价格是放在事件的参数传过来的。 lock(this) { if(ge!=null) messageBox.Text += "The Price is: " + ge.Price + System.Environment.NewLine; else messageBox.Text += "The Price is unknown"; } //下面屏蔽的程序段是把更新界面的动作转到界面专用线程上来执行。 //这样来避免多个线程对同一个变量进行改动的同步问题 /* String text; if(ge!=null) text = "The Price is: " + ge.Price + System.Environment.NewLine; else text = "The Price is unknown"; messageBox.Invoke(new MethodInvoker (new Updater(text,messageBox).update)); */ } class Updater { private String m_text; private Control m_control; public Updater(String text, Control control) { m_text = text; m_control = control; } public void update() { m_control.Text += m_text; } } //用户自定义的类。创建函数内将生成一个新的线程 class MyThread { private String m_stock; public event EventHandler gotPrice; public MyThread(String stockSymbol) { m_stock = stockSymbol; //生成一个线程,这个线程将执行getPrice()函数 new Thread(new ThreadStart(this.getPrice)).Start(); } public void getPrice() { localhost.StockPrice stockService = new localhost.StockPrice(); double price = stockService.getStockPrice(m_stock); //函数调用结束后,触发事件 gotPrice(this,new GotPriceEvent(price)); } } //用户自定义的事件参数 class GotPriceEvent : System.EventArgs { private double m_price; public GotPriceEvent(double price) { m_price = price; } public double Price { get {return m_price;} } } } |