这里有新鲜出炉的精品教程,程序狗速度看过来!
ASP.NET 是. NET FrameWork 的一部分,是一项微软公司的技术,是一种使嵌入网页中的脚本可由因特网服务器执行的服务器端脚本技术,它可以在通过 HTTP 请求文档时再在 web 服务器上动态创建它们。 指 Active Server Pages(动态服务器页面) ,运行于 IIS(Internet Information Server 服务,是 Windows 开发的 Web 服务器)之中的程序 。
这篇文章主要介绍了详解 ASP.NET MVC 下的异步 Action 的定义和执行原理,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
Visual Studio 提供的 Controller 创建向导默认为我们创建一个继承自抽象类 Controller 的 Controller 类型,这样的 Controller 只能定义同步 Action 方法。如果我们需要定义异步 Action 方法,必须继承抽象类 AsyncController。这篇问你讲述两种不同的异步 Action 的定义方法和底层执行原理。
一、基于线程池的请求处理
ASP.NET 通过线程池的机制处理并发的 HTTP 请求。一个 Web 应用内部维护着一个线程池,当探测到抵达的针对本应用的请求时,会从池中获取一个空闲的线程来处理该请求。当处理完毕,线程不会被回收,而是重新释放到池中。线程池具有一个线程的最大容量,如果创建的线程达到这个上限并且所有的线程均被处于 "忙碌" 状态,新的 HTTP 请求会被放入一个请求队列以等待某个完成了请求处理任务的线程重新释放到池中。
我们将这些用于处理 HTTP 请求的线程称为工作线程(Worker Thread),而这个县城池自然就叫做工作线程池。ASP.NET 这种基于线程池的请求处理机制主要具有如下两个优势:
如果请求处理操作耗时较短,那么工作线程处理完毕后可以及时地被释放到线程池中以用于对下一个请求的处理。但是对于比较耗时的操作来说,意味着工作线程将被长时间被某个请求独占,如果这样的操作访问比较频繁,在高并发的情况下意味着线程池中将可能找不到空闲的工作线程用于及时处理最新抵达请求。
如果我们采用异步的方式来处理这样的耗时请求,工作线程可以让后台线程来接手,自己可以及时地被释放到线程池中用于进行后续请求的处理,从而提高了整个服务器的吞吐能力。值得一提的是,异步操作主要用于 I/O 绑定操作(比如数据库访问和远程服务调用等),而非 CPU 绑定操作,因为异步操作对整体性能的提升来源于:当 I/O 设备在处理某个任务的时候,CPU 可以释放出来处理另一个任务。如果耗时操作主要依赖于本机 CPU 的运算,采用异步方法反而会因为线程调度和线程上下文的切换而影响整体的性能。
二、两种异步 Action 方法的定义
在了解了在 AsyncController 中定义异步 Action 方法的必要性之后,我们来简单介绍一下异步 Action 方法的定义方式。总的来说,异步 Action 方法具有两种定义方式,一种是将其定义成两个匹配的方法 XxxAsync/XxxCompleted,另一种则是定义一个返回类型为 Task 的方法。
XxxAsync/XxxCompleted
如果我们使用两个匹配的方法 XxxAsync/XxxCompleted 来定义异步 Action,我们可以将异步操作实现在 XxxAsync 方法中,而将最终内容的呈现实现在 XxxCompleted 方法中。XxxCompleted 可以看成是针对 XxxAsync 的回调,当定义在 XxxAsync 方法中的操作以异步方式执行完成后,XxxCompleted 方法会被自动调用。XxxCompleted 的定义方式和普通的同步 Action 方法比较类似。
作为演示,我在如下一个 HomeController 中定义了一个名为 Article 的异步操作来呈现指定名称的文章内容。我们将指定文章内容的异步读取定义在 ArticleAsync 方法中,而在 ArticleCompleted 方法中讲读取的内容以 ContentResult 的形式呈现出来。
- public class HomeController : AsyncController
- {
- public void ArticleAsync(string name)
- {
- AsyncManager.OutstandingOperations.Increment();
- Task.Factory.StartNew(() =>
- {
- string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\articles\{0}.html", name));
- using (StreamReader reader = new StreamReader(path))
- {
- AsyncManager.Parameters["content"] = reader.ReadToEnd();
- }
- AsyncManager.OutstandingOperations.Decrement();
- });
- }
- public ActionResult ArticleCompleted(string content)
- {
- return Content(content);
- }
- }
对于以 XxxAsync/XxxCompleted 形式定义的异步 Action 方法来说,ASP.NET MVC 并不会以异步的方式来调用 XxxAsync 方法,所以我们需要在该方法中自定义实现异步操作的执行。在上面定义的 ArticleAsync 方法中,我们是通过基于 Task 的并行编程方式来实现对文章内容的异步读取的。当我们以 XxxAsync/XxxCompleted 形式定义的异步 Action 方法的时候,会频繁地使用到 Controller 的 AsyncManager 属性,该属性返回一个类型为 AsyncManager 对象,我们将在下面一节对其进行单独讲述。
在上面提供的实例中,我们在异步操作开始和结束的时候调用了 AsyncManager 的 OutstandingOperations 属性的 Increment 和 Decrement 方法对于 ASP.NET MVC 发起通知。此外,我们还利用 AsyncManager 的 Parameters 属性表示的字典来保存传递给 ArticleCompleted 方法的参数,参数在字典中的 Key(content)与 ArticleCompleted 的参数名称是匹配的,所以在调用方法 ArticleCompleted 的时候,通过 AsyncManager 的 Parameters 属性指定的参数值将自动作为对应的参数值。
Task 返回值
如果采用上面的异步 Action 定义方式,意味着我们不得不为一个 Action 定义两个方法,实际上我们可以通过一个方法来完成对异步 Action 的定义,那就是让 Action 方法返回一个代表异步操作的 Task 对象。上面通过 XxxAsync/XxxCompleted 形式定义的异步 Action 可以采用如下的定义方式。
- public class HomeController AsyncController
- {
- public Task<ActionResult> Article(string name)
- {
- return Task.Factory.StartNew(() =>
- {
- string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\articles\{0}.html", name));
- using (StreamReader reader = new StreamReader(path))
- {
- AsyncManager.Parameters["content"] = reader.ReadToEnd();
- }
- }).ContinueWith<ActionResult>(task =>
- {
- string content = (string)AsyncManager.Parameters["content"];
- return Content(content);
- });
- }
- }
上面定义的异步 Action 方法 Article 的返回类型为 Task<ActionResult>,我们将异步文件内容的读取体现在返回的 Task 对象中。对文件内容呈现的回调操作则通过调用该 Task 对象的 ContinueWith<ActionResult> 方法进行注册,该操作会在异步操作完成之后被自动调用。
如上面的代码片断所示,我们依然利用 AsyncManager 的 Parameters 属性实现参数在异步操作和回调操作之间的传递。其实我们也可以使用 Task 对象的 Result 属性来实现相同的功能,Article 方法的定义也改写成如下的形式。
- public class HomeController AsyncController
- {
- public Task<ActionResult> Article(string name)
- {
- return Task.Factory.StartNew(() =>
- {
- string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\articles\{0}.html", name));
- using (StreamReader reader = new StreamReader(path))
- {
- return reader.ReadToEnd();
- }
- }).ContinueWith<ActionResult>(task =>
- {
- return Content((string)task.Result);
- });
- }
- }
三、AsyncManager
在上面演示的异步 Action 的定义中,我们通过 AsyncManager 实现了两个基本的功能,即在异步操作和回调操作之间传递参数和向 ASP.NET MVC 发送异步操作开始和结束的通知。由于 AsyncManager 在异步 Action 场景中具有重要的作用,我们有必要对其进行单独介绍,下面是 AsyncManager 的定义。
- public class AsyncManager
- {
- public AsyncManager();
- public AsyncManager(SynchronizationContext syncContext);
- public EventHandler Finished;
- public virtual void Finish();
- public virtual void Sync(Action action);
- public OperationCounter OutstandingOperations { get; }
- public IDictionary<string, object> Parameters { get; }
- public int Timeout { get; set; }
- }
- public sealed class OperationCounter
- {
- public event EventHandler Completed;
- public int Increment();
- public int Increment(int value);
- public int Decrement();
- public int Decrement(int value);
- public int Count { get; }
- }
如上面的代码片断所示,AsyncManager 具有两个构造函数重载,非默认构造函数接受一个表示同步上下文的 SynchronizationContext 对象作为参数。如果指定的同步上下文对象为 Null,并且当前的同步上下文(通过 SynchronizationContext 的静态属性 Current 表示)存在,则使用该上下文;否则创建一个新的同步上下文。该同步上下文用于 Sync 方法的执行,也就是说在该方法指定的 Action 委托将会在该同步上下文中以同步的方式执行。
AsyncManager 的核心是通过属性 OutstandingOperations 表示的正在进行的异步操作计数器,该属性是一个类型为 OperationCounter 的对象。操作计数通过只读属性 Count 表示,当我们开始和完成异步操作的时候分别调用 Increment 和 Decrement 方法作增加和介绍计数操作。Increment 和 Decrement 各自具有两个重载,作为整数参数 value(该参数值可以是负数)表示增加或者减少的数值,如果调用无参方法,增加或者减少的数值为 1。如果我们需要同时执行多个异步操作,则可以通过如下的方法来操作计数器。
- AsyncManager.OutstandingOperations.Increment(3);
- Task.Factory.StartNew(() =>
- {
- //异步操作1
- AsyncManager.OutstandingOperations.Decrement();
- });
- Task.Factory.StartNew(() =>
- {
- //异步操作2
- AsyncManager.OutstandingOperations.Decrement();
- });
- Task.Factory.StartNew(() =>
- {
- //异步操作3
- AsyncManager.OutstandingOperations.Decrement();
- });
对于每次通过 Increment 和 Decrement 方法调用引起的计数数值的改变,OperationCounter 对象都会检验当前计数数值是否为零,如果则表明所有的操作运行完毕,如果预先注册了 Completed 事件,该事件会被触发。值得一提的时候,表明所有操作完成执行的标志是计数器的值等于零,而不是小于零,如果我们通过调用 Increment 和 Decrement 方法使计数器的值称为一个负数,注册的 Completed 事件是不会被触发的。
AsyncManager 在初始化的时候就注册了通过属性 OutstandingOperations 表示的 OperationCounter 对象的 Completed 事件,使该事件触发的时候调用自身的 Finish 方法。而虚方法 Finish 在 AsyncManager 中的默认实现又会触发自身的 Finished 事件。
如下面的代码片断所示,Controller 类实现了 IAsyncManagerContainer 接口,而后者定义了一个只读属性 AsyncManager 用于提供辅助执行异步 Action 的 AsyncManager 对象,而我们在定义异步 Action 方法是使用的 AsyncManager 对象就是从抽象类 Controller 中集成下来的 AsyncManager 属性。
- public abstract class Controller ControllerBase, IAsyncManagerContainer,...
- {
- public AsyncManager AsyncManager { get; }
- }
- public interface IAsyncManagerContainer
- {
- AsyncManager AsyncManager { get; }
- }
四、Completed 方法的执行
对于通过 XxxAsync/XxxCompleted 形式定义的异步 Action,我们说回调操作 XxxCompleted 会在定义在 XxxAsync 方法中的异步操作执行结束之后被自动调用,那么 XxxCompleted 方法具体是如何被执行的呢?
异步 Action 的执行最终是通过描述该 Action 的 AsyncActionDescriptor 对象的 BeginExecute/EndExecute 方法来完成的。通过之前 "Model 的绑定" 的介绍我们知道通过 XxxAsync/XxxCompleted 形式定义的异步 Action 通过一个 ReflectedAsyncActionDescriptor 对象来表示的,ReflectedAsyncActionDescriptor 在执行 BeginExecute 方法的时候会注册 Controller 对象的 AsyncManager 的 Finished 事件,使该事件触发的时候去执行 Completed 方法。
也就是说针对当前 Controller 的 AsyncManager 的 Finished 事件的触发标志着异步操作的结束,而此时匹配的 Completed 方法会被执行。由于 AsyncManager 的 Finish 方法会主动触发该事件,所以我们可以通过调用该方法使 Completed 方法立即执行。由于 AsyncManager 的 OperationCounter 对象的 Completed 事件触发的时候会调用 Finish 方法,所以当表示当前正在执行的异步操作计算器的值为零时,Completed 方法也会自动被执行。
如果我们在 XxxAsync 方法中通过如下的方式同时执行三个异步操作,并在每个操作完成之后调用 AsyncManager 的 Finish 方法,意味着最先完成的异步操作会导致 XxxCompleted 方法的执行。换句话说,当 XxxCompleted 方法执行的时候,可能还有两个异步操作正在执行。
- AsyncManager.OutstandingOperations.Increment(3);
- Task.Factory.StartNew(() =>
- {
- //异步操作1
- AsyncManager.Finish();
- });
- Task.Factory.StartNew(() =>
- {
- //异步操作2
- AsyncManager.Finish();
- });
- Task.Factory.StartNew(() =>
- {
- //异步操作3
- AsyncManager.Finish();
- });
如果完全通过为完成的异步操作计数机制来控制 XxxCompleted 方法的执行,由于计数的检测和 Completed 事件的触发只发生在 OperationCounter 的 Increment/Decrement 方法被执行的时候,如果我们在开始和结束异步操作的时候都没有调用这两个方法,XxxCompleted 是否会执行呢?同样以之前定义的用语读取 / 显示文章内容的异步 Action 为例,我们按照如下的方式将定义在 ArticleAsync 方法中针对 AsyncManager 的 OutstandingOperations 属性的 Increment 和 Decrement 方法调用注释调用,ArticleCompleted 方法是否还能正常运行呢?
- public class HomeController AsyncController
- {
- public void ArticleAsync(string name)
- {
- //AsyncManager.OutstandingOperations.Increment();
- Task.Factory.StartNew(() =>
- {
- string path = ControllerContext.HttpContext.Server.MapPath(string.Format(@"\articles\{0}.html", name));
- using (StreamReader reader = new StreamReader(path))
- {
- AsyncManager.Parameters["content"] = reader.ReadToEnd();
- }
- //AsyncManager.OutstandingOperations.Decrement();
- });
- }
- public ActionResult ArticleCompleted(string content)
- {
- return Content(content);
- }
- }
实际上 ArticleCompleted 依然会被执行,但是这样我们就不能确保正常读取文章内容,因为 ArticleCompleted 方法会在 ArticleAsync 方法执行之后被立即执行。如果文章内容读取是一个相对耗时的操作,表示文章内容的 ArticleCompleted 方法的 content 参数在执行的时候尚未被初始化。在这种情况下的 ArticleCompleted 是如何被执行的呢?
原因和简单,ReflectedAsyncActionDescriptor 的 BeginExecute 方法在执行 XxxAsync 方法的前后会分别调用 AsyncManager 的 OutstandingOperations 属性的 Increment 和 Decrement 方法。对于我们给出的例子来说,在执行 ArticleAsync 之前 Increment 方法被调用使计算器的值变成 1,随后 ArticleAsync 被执行,由于该方法以异步的方式读取指定的文件内容,所以会立即返回。最后 Decrement 方法被执行使计数器的值变成 0,AsyncManager 的 Completed 事件被触发并导致 ArticleCompleted 方法的执行。而此时,文件内容的读取正在进行之中,表示文章内容的 content 参数自然尚未被初始化。
ReflectedAsyncActionDescriptor 这样的执行机制也对我们使用 AsyncManager 提出了要求,那就是对尚未完成的一步操作计数器的增加操作不应该发生在异步线程中,如下所示的针对 AsyncManager 的 OutstandingOperations 属性的 Increment 方法的定义是不对的。
- public class HomeController AsyncController
- {
- public void XxxAsync(string name)
- {
- Task.Factory.StartNew(() =>
- {
- AsyncManager.OutstandingOperations.Increment();
- //...
- AsyncManager.OutstandingOperations.Decrement();
- });
- }
- //其他成员
- }
下面采用正确的定义方法:
- public class HomeController AsyncController
- {
- public void XxxAsync(string name)
- {
- AsyncManager.OutstandingOperations.Increment();
- Task.Factory.StartNew(() =>
- {
- //...
- AsyncManager.OutstandingOperations.Decrement();
- });
- }
- //其他成员
- }
最后再强调一点,不论是显式调用 AsyncManager 的 Finish 方法,还是通过调用 AsyncManager 的 OutstandingOperations 属性的 Increment 方法是计数器的值变成零,仅仅是让 XxxCompleted 方法得以执行,并不能真正阻止异步操作的执行。
五、异步操作的超时控制
异步操作虽然适合那些相对耗时的 I/O 绑定型操作,但是也并不说对一步操作执行的时间没有限制。异步超时时限通过 AsyncManager 的整型属性 Timeout 表示,它表示超时时限的总毫秒数,其默认值为 45000(45 秒)。如果将 Timeout 属性设置为 - 1,意味着异步操作执行不再具有任何时间的限制。对于以 XxxAsync/XxxCompleted 形式定义的异步 Action 来说,如果 XxxAsync 执行之后,在规定的超时时限中 XxxCompleted 没有得到执行,一个 TimeoutException 会被抛出来。
如果我们以返回类型为 Task 的形式定义异步 Action,通过 Task 体现的异步操作的执行时间不受 AsyncManager 的 Timeout 属性的限制。我们通过如下的代码定义了一个名为 Data 的异步 Action 方法以异步的方式获取作为 Model 的数据并通过默认的 View 呈现出来,但是异步操作中具有一个无限循环,当我们访问该 Data 方法时,异步操作将会无限制地执行下去,也不会有 TimeoutException 异常发生。
- public class HomeController AsyncController
- {
- public Task<ActionResult> Data()
- {
- return Task.Factory.StartNew(() =>
- {
- while (true)
- { }
- return GetModel();
- }).ContinueWith<ActionResult>(task =>
- {
- object model = task.Result;
- return View(task.Result);
- });
- }
- //其他成员
- }
在 ASP.NET MVC 应用编程接口中具有两个特殊的特性用于定制异步操作执行的超时时限,它们是具有如下定义的 AsyncTimeoutAttribute 和 NoAsyncTimeoutAttribute,均定义在命名空间 System.Web.Mvc 下。
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
- public class AsyncTimeoutAttribute ActionFilterAttribute
- {
- public AsyncTimeoutAttribute(int duration);
- public override void OnActionExecuting(ActionExecutingContext filterContext);
- public int Duration { get; }
- }
- [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited=true, AllowMultiple=false)]
- public sealed class NoAsyncTimeoutAttribute AsyncTimeoutAttribute
- {
- // Methods
- public NoAsyncTimeoutAttribute() base(-1)
- {
- }
- }
从上面给出的定义我们可以看出这两个特性均是 ActionFilter。AsyncTimeoutAttribute 的构造函数接受一个表示超时时限(以毫秒为单位)的整数作为其参数,它通过重写 OnActionExecuting 方法将指定的超时时限设置给当前 Controller 的 AsyncManager 的 Timeout 属性进行。NoAsyncTimeoutAttribute 是 AsyncTimeoutAttribute 的继承者,它将超时时限设置为 - 1,意味着它解除了对超时的限制。
从应用在这两个特性的 AttributeUsageAttribute 定义可看出,它们既可以应用于类也可以用于也方法,意味着我们可以将它们应用到 Controller 类型或者异步 Action 方法(仅对 XxxAsync 方法有效,不能应用到 XxxCompleted 方法上)。如果我们将它们同时应用到 Controller 类和 Action 方法上,针对方法级别的特性无疑具有更高的优先级。
来源: http://www.phperz.com/article/17/0813/338080.html