ASP.NET MVC开发专题博客

ASP.NET MVC开发专题博客,为您精选ASP.NET MVC开发教程,助您开发愉快!

公告信息
欢迎光临ASP.NET MVC开发专题博客,祝您开发愉快!
文章档案
最新评论

ASP.NET MVC Controller异步机制-MVC原理系列9


上节:ASP.NET MVC深入Filter过滤器上下文参数-MVC原理系列8,我们深入讲述了ASP.NET MVC中的4种Filter过滤器及上下文参数。

本节,我们来深入讲述一下:ASP.NET MVC Controller异步机制

正文如下:

ASP.NET MVC异步处理请求的意义

大伙儿都知道,ASP.NET通过线程池处理请求,对于每个请求从线程池中请求一个可用的线程来处理请求,当请求处理完毕之后,线程资源将被归还到线程池。然而,线程池中的线程是互斥资源,当网站在同一时刻的请求量达到一定数量的话,必然会导致这种资源不够耗尽,新来的请求只能等待有新的线程归还才能被处理。当然这不是最糟糕的,通常每个请求只需要很短的时间就可以了,新的请求不会等待太长的时间,但是,如果处理请求需要花费较长的时间呢?比如一次耗时的数据库查询、一次外部web service请求这类的IO操作。注意这里特指的IO操作,指的是不会占用ASP.NET线程池线程的,甚至不占用本机CPU资源的操作。正因为如此,异步处理请求在这种情况下尤其适用。当异步处理请求时,占用的线程会在耗时的IO操作开始前,将线程归还给线程池,直到IO操作完成后,再从线程池中请求一个线程,并恢复当时的HttpContext,处理IO操作的结果。这样就不会占用宝贵的线程资源了。

 

ASP.NET MVC中的异步Controller机制

MVC支持异步地处理请求。可以通过下面的三种方式:

  1. 实现一个自定义的RouteHandler,并为GetHttpHandler方法返回一个实现IHttpAsynHandler的对象。
  2. 创建一个自定义的基类Controller,并实现IAsyncController,IAsyncController是IController的异步版本。
  3. MVC内置了一个AsyncController,它实现了上述的IAsyncController,通过简单的继承AsyncController,即可实现异步。

下面仅对第三种方法作简单介绍。假设某个Action需要调用一个web service,并处理结果后返回:

01public ContentResult GetPhotoByTag(string tag)
02{
03    ...
04    using (var response = WebRequest.Create(url).GetResponse())
05    {
06        // Parse the response as XML
07        var xmlDoc = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
08        ...
09    }
10    ...
11}

显然,如果这个web request消耗2s,那么这个请求将hold这个线程至少2s。这种同步的处理方式显然不合理,想要异步处理,只需按如下步骤进行:

1、替换基类Controller为AsyncController

2、创建两个配对的Action:ActionNameAsync和ActionNameCompletedActionNameAsync方法必须返回void,在内部启动一个耗时的IO操作前,需要使用AsyncManager.OutstandingOperations.Increment()向MVC框架“注册启动”,在IO方法返回后,可以在AsyncManager.Parameters字典中保存希望传给ActionNameCompleted方法的参数。最后调用AsyncManager.OutstandingOperations.Decrement()通知MVC框架操作完成,此时,MVC框架会自动调用ActionNameCompleted。ActionNameCompleted需要向通常的Action一样,返回一个ActionResult。因此上面的代码需要改写成如下这样:

01public void GetPhotoByTagAsync(string tag)
02{
03    //向MVC中注册启动
04    AsyncManager.OutstandingOperations.Increment();
05    ...
06    WebRequest request = WebRequest.Create(url);
07    //启动一个异步的web request
08    request.BeginGetResponse(asyncResult =>
09    {
10        using (WebResponse response = request.EndGetResponse(asyncResult))
11        {
12            var xml = XDocument.Load(XmlReader.Create(response.GetResponseStream()));
13            ...
14            //将结果photoUrls,保存在AsyncManager.Parameters中
15            AsyncManager.Parameters["photoUrls"] = photoUrls;
16            //通知MVC框架操作完成 ,准备调用Completed
17            AsyncManager.OutstandingOperations.Decrement();
18        }
19    }, null);
20}
21//像通常的Action一样,这里的参数photoUrls将在AsyncManager.Parameters中匹配
22public ContentResult GetPhotoByTagCompleted(IEnumerable<string> photoUrls)
23{
24    return Content(string.Format("<img src='{0}'/>", photoUrls.First()));
25}

当然,可以设置异步操作的超时时间:

1[AsyncTimeout(10000)] // 10000 milliseconds equals 10 seconds
2public void GetPhotoByTagAsync(string tag) { ... }

上面的代码如果超时了,将抛出TimeoutException异常,我们可以用希望的方式处理它。

当使用类似BeginGetResponse这类的异步方法,并提供回调函数参数时,你无法控制回调函数调用在哪个线程上。大多数情况下,甚至不在ASP.NET的工作线程上。所以回调函数无法关联原始的HttpContext对象。

幸好,AsyncManager提供了一个Sync()方法,它会将一个委托在ASP.NET的工作线程上启动,并关联原始的HttpContext对象。而且它保证线程安全:

01BeginAsyncOperation(asyncResult => {
02    var result = EndAsyncOperation(asyncResult);
03    // Can't always access System.Web.HttpContext.Current from here...
04    Action doSomethingWithHttpContext = () => {
05    // ... but can always access it from this delegate
06};
07if (asyncResult.CompletedSynchronously) // Already on an ASP.NET thread
08    doSomethingWithHttpContext();
09else // Must switch to an ASP.NET thread
10    AsyncManager.Sync(doSomethingWithHttpContext);
11AsyncManager.OutstandingOperations.Decrement();
12}, null);

 

如果涉及数据库操作,推荐一款配套的ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"

2011/9/8 0:50:35 | ASP.NET MVC教程 | |

#1游客[注册][60.178.127.*]2016/6/28 16:09:30
从MvcHandler一直到ActionResault的执行,整个过程很多地方都用了异步执行的方式,如果单独为了访问吞吐量只要在MvcHandler用异步执行不就好了吗?后续的步骤不应该是按顺序同步执行的么?为什么也用异步呢?
  • 发表评论