你应该知道的 asp.net webform之异步页面

      对于搞asp.net的程序员,都知道所有的服务请求最终都会有一个IhttpHandler来处理,就像我们最常用的aspx页面。相对于IHttpHandler,asp.net还提供了一个异步的相同版本的处理程序接口,它就是IHttpAsyncHandler,同样asp.net也可以让我们的aspx页面实现IHttpAsyncHandler,而不仅仅是IHttpHandler。

 

为什么要异步页面

      我们都知道asp.net维护一个处理页面请求的线程池,每一个新的请求,asp.net就会从其中取出一个空闲的线程来实例化页面,运行处理代码然后呈现HTML,然后返回线程池,等待下一次被激活。但是如果请求到来的过于频繁,比我们线程处理页面返回时间还短,那么这个请求就会被放到一个队列里,如果队列满了,就会产生一个503的服务不可用来拒绝其它的请求。

      可以想象,如果我们的页面在等待一个慢的服务器在处理大量的数据、读取远程文件或一个WEB服务返回数据,这时我们页面没有代码要执行,但是这个线程会被挂起,这就会严重的消耗可用线程,影响网站并发。

      而通过异步页面, 我们可以把这些耗时的处理迁移到其它线程池,而这些异步工作完成时,asp.net会接到通知,再次从线程池激活一个可用线程,处理余下的工作,最终呈现HTML。

 

创建异步页面

      创建异步页面,远比我们想象简单的多,我们首先要在Page指令加一个Async的特性,并把它设为true.

    <%@ Page Async="true" AsyncTimeout="60" ...

还有一个timeout的特性用来指定异步的超时时间,单位是s,默认是45s。

 

      接下来,我们需要调用AddOnPreRenderCompleteAsync注册异步处理:

    protected void Page_Load(object sender, EventArgs e)

    {

        AddOnPreRenderCompleteAsync(new BeginEventHandler(BeginHandler), new EndEventHandler(EndHandler));

    }

 

AddOnPreRenderCompleteAsync还提供另一个重载的版本

public void AddOnPreRenderCompleteAsync(

	BeginEventHandler beginHandler,

	EndEventHandler endHandler,

	Object state

)

首先,开始启动异步任务的委托和处理异步结束时的回调是不可少的,另一个参数,让我们可以传递一些状态的信息给异步开始的方法。

 

异步页面的执行

       在我们展示BeginHandler、EndHandler之前,让我们通过下面转载自MSDN的一张图,看一下异步处理是如何工作的:

你应该知道的 asp.net webform之异步页面

 

      我们可以看出我们注册的BeginHandler在prePrend之后才开始执行,这时线程已经回到线程池,代码的处理交到了BeginHandler,我们必须在这里开始一个异步的处理,处理完后返回IAsyncResult的结果,随后EndHandler被调用,之后,线程池的另一个线程被激活,接着处理页面流程。

 

有效的异步处理

      到了这里,你可能感觉到异步页面分明就是一个坑啊,到了最后还是要我们自己去实现异步处理一个耗时的操作。

     但是这可能对于我们来说算不上什么啊,我们有很多种方法开始异步的处理啊,ThreadPool.QueueUserWorkItem,Thread类创建一个专用线程、委托的BeginInvoke和类库中内建的异步支持,如Command的BeginExecuteReader,但是我们能选择的却不是那么多。

 

      第一类,委托的BeginInvoke和ThreadPool.QueueUserWorkItem,这两个会从asp.net请求线程池中激活线程来处理,这就是相同于释放一个线程的同时又从线程池拿一个线程出来,这不是脱裤子放屁吗?一点也起不到增强网站并发处理的能力,还无谓的增加了线程调度的浪费。

 

      第二类,Thread类创建专用线程,这样做可以达到目的,并且可以做到服务器不能处理的工作,但这是非常危险的。如果这样的请求过于和频繁,创建出过多的这样的线程,这对服务器是一种压力,很可能导致服务器再也不能处理其它的请求了。当然,你可以实现一个自定义的线程池来管理这些线程,让他保持在一个合适的范围,并且总是有可用的线程可用,但是这个开发代价就太大了。

 

      接下来就只有.net内置如数据Command的BeginExecuteReader、IO的BeginRead和BeginWriter等处理异步的支持了,其实这也是我们最应该也最值得用的异步方式。让.net去管理线程的问题,又不会从当前请求线程池中拿线程,使用起来也简单强大。

 

异步的实现

      接着上面的代码,我们贴出BeginHandler、EndHandler代码,只是提供一个例子:

    private SqlConnection con;

    private SqlCommand cmd;



    private IAsyncResult BeginHandler(Object obj, EventArgs args, AsyncCallback cb, Object state)

    {

        string conStr = "";



        con = new SqlConnection(conStr);

        cmd = new SqlCommand("select * from ...", con);



        con.Open();



        return cmd.BeginExecuteReader(cb, state);



    }



    private void EndHandler(IAsyncResult ar)

    {

        try

        {

            SqlDataReader reader = cmd.EndExecuteReader(ar);

            ……………

        }

        catch (Exception ex)

        { 

            //  错误处理

        }

    }

 

      这样就实现了一个简单的异步页面的模型,对于这些耗时的操作,我们可能会使用到缓存,这样我们自定义一个实现了IAsyncResult的类,包含我们要使用的数据,在BeginHandler里判断缓存是否存在,如果存在返回自定义实例,并用缓存填充这个实例,不存在就执行异步操作;而在EndHandler里区分出返回实例,使用数据再更新缓存。

 

多异步任务

     如果要处理多个Web服务或者同时去等待web服务,还有数据库操作等等,这时,我们怎么做?

 

1,我们可以调用多次AddOnPreRenderCompleteAsync,每次传入对应的begin和end的委托,但是注册的多个任务是顺序执行的,也就是只有处理完第一个任务end执行过后,才会开始执行第二个任务。

 

2,我们只调用一次AddOnPreRenderCompleteAsync,在begin里启动多少异步操作,但是这个操作会有太多的局限性,并且会更复杂。

 

3,不出大家意外,asp.net提供了处理这样的方法。就是如下的方式:

            PageAsyncTask taska = new PageAsyncTask(

                new BeginEventHandler(BeginHandler1), 

                new EndEventHandler(EndHandler1), 

                null, 

                null

                );



            Page.RegisterAsyncTask(taska);



            PageAsyncTask taskb = new PageAsyncTask(

                new BeginEventHandler(BeginHandler2),

                new EndEventHandler(EndHandler2),

                null,

                null

                );



            Page.RegisterAsyncTask(taskb);

这样的注册异步任务会同时执行,当所有的异步都执行完毕,才会开始余下的页面的流程。

 

最后我们看一下PageAsyncTask的重载版本:

public PageAsyncTask (

	BeginEventHandler beginHandler,

	EndEventHandler endHandler,

	EndEventHandler timeoutHandler,

	Object state,

	bool executeInParallel

)

除了前两个任务开始和结束调用操作参数之外,还提供了一个超时时的处理程序、一个球表示任务状态的对象和一个是否要和其它任务同时执行的布尔值。

 

最后

没有最好,只有最适合,任何一种的处理方式都不会是完美的,就像Jeffrey大师在异步操作中所说的:应尽可能限制线程的使用。异步页面虽然可以让我们网站可以处理更多的请求,但是它并不会让你的用户感觉到页面的呈现变快,甚至会慢些,因为创建线程本身就会产生一定的消耗,并且线程之间的切换开销也相当大。切记。

你可能感兴趣的:(asp.net)