异步页面,增加网站伸缩性,增强用户体验

[ 2008-2-20 14:59:00 | By: 99love ]

更易阅读的版本:http://ri3.org/post/2008/02/async-page.aspx

先举个例子,你现在有三件事情要做:
1、下载一个软件。完成这件事情,假如需要2个小时。
2、吃饭,需要30分钟。
3、玩游戏1个半小时。
如果你按同步的方式来完成这件事,需要2+0.5+1.5=4 小时。你会很奇怪吧,为什么要等到下载完成后再吃饭呢,吃饭时不能玩游戏,我同意,但是下载的时候你又没其它事做,就可以吃饭呀。
很好,因为你是想用异步的思想来完成这三件事:
1、配置flashget,使得它下载完成后发出滴的响声,这样下载完成你就知道了,以便进行后续操作。(1分钟)
2、打开Flashget,新建任务,开始下载。(1分钟)
3、开始下载后,立刻去吃饭。吃完了饭,开始玩游戏。(30分钟)
4、游戏中听到滴的一声,你知道下载完成,进行下载的收尾工作。(从开始到下载完成用了2个小时)

完成所有的操作,你只需要2个小时的时间,而这两个小时是完成下载所必需的时间,吃饭,玩游戏都在这个过程中做了。

ASP.NET的异步页面跟这个例子类似。一个异步 I/O 操作响应来自 ASP.NET 的信号并开始进行,处理该操作的线程会返回原先的线程池。操作完成后,另一个线程会来完成处理请求,这就是异步页面。它使得线程池的线程使用率得到提高,如果原先经常出现503“服务器不可用”错误,现在也许就不会了。
你的页面中涉及到一个长时间的操作,如果你使得处理该操作的线程挂起,等待该操作结束的话,这是很不明智的,你应该使用异步操作来完成它:先使得一个线程来开始处理这个请求,并注册一个Callback,这相当于Flashget下载完成会发出的滴的一声,如果Flashget发出了“滴”,你知道它下载完成了,你就会去完成最初这个下载请求,比如说,把下载好的文件放到E盘下。
原理就是这样,如何进行异步操作?
比如说你有一个函数叫dowork,它的函数签名是这样void dowork(string),调用这个函数需要一个string参数。
如果它是完成复杂的运行,或者在请求web services的话,完成dowork的时间会很长很长。
大概需要如下几步:
1、为你的dowork函数量身定做一个委托(delegate)

 

private delegate void doWorkDelegate(string para);


2、用你的函数dowork实例化一个委托。

doWorkDelegate dowork1 = new doWorkDelegate(dowork);


3、调用实例化的delegate的BeginInvoke方法,在其中传递调用dowork的参数和注册回调函数callback。

 

dowork1.BeginInvoke(para, new AsyncCallback(callback), null);


4、在callback中记得EndInvoke,并进行其它收尾工作。

    protected void callback(IAsyncResult ar)

    {

        dowork1.EndInvoke(iar);

    }


看一段完整后台的代码:

 
   

    private delegate void doWorkDelegate(string para);//dowork方法量身定做一个delegate

    private doWorkDelegate dowork1 = null;//用该delegate声明的一具体的委托dowork1

    protected IAsyncResult iar;//这个callback需要调用的参数

    protected void Page_Load(object sender, EventArgs e)

    {

        btnAsync.Click += new EventHandler(NotDelayRender);//这个不管,是在page_load注册onclick,与现在的主题无关

    }

    protected void NotDelayRender(object sender, EventArgs e)

    {

        //here is some para for dowork function

        dowork1 = new doWorkDelegate(dowork);//dowork1实例化,将要处理的函数dowork交给它。

        string para = "parameter";//假如我们需要一个参数,这个参数的值随便从哪里获得都行,这个值,会作为参数传入dowork方法的

        iar = dowork1.BeginInvoke(para, new AsyncCallback(callback), null);//传递para,注册callback

    }

    protected void callback(IAsyncResult ar)

    {

        //函数的参数ar中有一个ar.AsyncState,这个AsyncStateBeginInvoke的最后参数相关的,最后一个参数是什么,ar.AsyncState就会是什么

        dowork1.EndInvoke(iar);//callback中完成异步,EndInvoke的参数iarBeginInvoke的返回值,可以不是callback的参数ar,要注意。

        //dowork已经完成,你还可以在这里写点其它代码

        //比如说写数据库什么的。

    }

    protected void dowork(string para)

    {

        //do some work here

        //we need a long time to execute this function.

        System.Threading.Thread.Sleep(5000);

        //done

    }

你在页面上运行这段代码(上面的代码不是全部的代码,没有前台的)
上面的运行效果是:点击btnAsync,一瞬间页面载入完成,虽然在dowork中线程会sleep5秒钟,但是由于是异步的,所以页面不会等待那5秒钟时间,立刻完成。
这就好像你开始下载后,立刻返回,又处理下一件事情,吃饭去了。

注意使用这种方法不会延迟生成页面的时间,也就是说,你在callback中向前台输出内容的代码都不会生效了,因为页面Render已经完成。
你如果再写lblMessage.Text="完成";这些代码,前台都不会有反应了。

如果你还需要向前台输出内容的话,你就必须延迟页面的Render时间,具体方法请自行参看MSDN上的文章:ASP.NET 2.0 中的异步页面

这种异步方法可以使用用户在请求的一个很长时间的请求时,不必长时间枯燥的等待,实际上,用户请求之后,这个操作已经在继续了,只是页面返回了而已。更重要的是,服务器上线程池的线程使用率得到提高,服务器伸缩性更强。
如果你要更优化用户体验,你可以用ajax查询dowork的进度,在dowork方法中保存当前进度到服务器上(Session/Cache等等), ajax可以来查询进度,Ajax的ICallbackEventHandler用法可以参数这篇文章:http://99love.blueidea.com/archives/2008/5970.shtml

发表评论:

    密码:
    主页:
    标题:
    页面数据正在载入...
bxna 京ICP备05002321号