您的位置:首页 > 编程语言 > ASP

ASP.NET MVC下的异步Action的定义和执行原理

2012-06-20 13:33 453 查看
[code] 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);

}

}

[/code]
[/code]
对于以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可以采用如下的定义方式。


[code][code] public class HomeController : AsyncController

{

public Task<ActionResult> Article(string name)

{

returnTask.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);

});

}

}

[/code]
[/code]
上面定义的异步Action方法Article的返回类型为Task<ActionResult>,我们将异步文件内容的读取体现在返回的Task对象中。对文件内容呈现的回调操作则通过调用该Task对象的ContinueWith<ActionResult>方法进行注册,该操作会在异步操作完成之后被自动调用。

如上面的代码片断所示,我们依然利用AsyncManager的Parameters属性实现参数在异步操作和回调操作之间的传递。其实我们也可以使用Task对象的Result属性来实现相同的功能,Article方法的定义也改写成如下的形式。


[code][code] public class HomeController : AsyncController

{

public Task<ActionResult> Article(string name)

{

returnTask.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);

});

}

}

[/code]
[/code]

三、AsyncManager

在上面演示的异步Action的定义中,我们通过AsyncManager实现了两个基本的功能,即在异步操作和回调操作之间传递参数和向ASP.NET MVC发送异步操作开始和结束的通知。由于AsyncManager在异步Action场景中具有重要的作用,我们有必要对其进行单独介绍,下面是AsyncManager的定义。

[code]
[code] 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;}

}

[/code]
[/code]
如上面的代码片断所示,AsyncManager具有两个构造函数重载,非默认构造函数接受一个表示同步上下文的SynchronizationContext对象作为参数。如果指定的同步上下文对象为Null,并且当前的同步上下文(通过SynchronizationContext的静态属性Current表示)存在,则使用该上下文;否则创建一个新的同步上下文。该同步上下文用于Sync方法的执行,也就是说在该方法指定的Action委托将会在该同步上下文中以同步的方式执行。

AsyncManager的核心是通过属性OutstandingOperations表示的正在进行的异步操作计数器,该属性是一个类型为OperationCounter的对象。操作计数通过只读属性Count表示,当我们开始和完成异步操作的时候分别调用Increment和Decrement方法作增加和介绍计数操作。Increment和Decrement各自具有两个重载,作为整数参数value(该参数值可以是负数)表示增加或者减少的数值,如果调用无参方法,增加或者减少的数值为1。如果我们需要同时执行多个异步操作,则可以通过如下的方法来操作计数器。

[code]
[code] 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();

});

[/code]
[/code]
对于每次通过Increment和Decrement方法调用引起的计数数值的改变,OperationCounter对象都会检验当前计数数值是否为零,如果则表明所有的操作运行完毕,如果预先注册了Completed事件,该事件会被触发。值得一提的时候,表明所有操作完成执行的标志是计数器的值等于零,而不是小于零,如果我们通过调用Increment和Decrement方法使计数器的值称为一个负数,注册的Completed事件是不会被触发的。

AsyncManager在初始化的时候就注册了通过属性OutstandingOperations表示的OperationCounter对象的Completed事件,使该事件触发的时候调用自身的Finish方法。而虚方法Finish在AsyncManager中的默认实现又会触发自身的Finished事件。

如下面的代码片断所示,Controller类实现了IAsyncManagerContainer接口,而后者定义了一个只读属性AsyncManager用于提供辅助执行异步Action的AsyncManager对象,而我们在定义异步Action方法是使用的AsyncManager对象就是从抽象类Controller中集成下来的AsyncManager属性。

[code]
[code] public abstract class Controller : ControllerBase, IAsyncManagerContainer,...

{

 public AsyncManager AsyncManager{ get;}

}


public interface IAsyncManagerContainer

{

 AsyncManager AsyncManager{ get;}

}

[/code]
[/code]

四、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方法执行的时候,可能还有两个异步操作正在执行。

[code]
[code] AsyncManager.OutstandingOperations.Increment(3);


Task.Factory.StartNew(() =>

{

 //异步操作1

 AsyncManager.Finish();

});

Task.Factory.StartNew(() =>

{

 //异步操作2

 AsyncManager.Finish();

});

Task.Factory.StartNew(() =>

{

 //异步操作3

 AsyncManager.Finish();

});

[/code]
[/code]
如果完全通过为完成的异步操作计数机制来控制XxxCompleted方法的执行,由于计数的检测和Completed事件的触发只发生在OperationCounter的Increment/Decrement方法被执行的时候,如果我们在开始和结束异步操作的时候都没有调用这两个方法,XxxCompleted是否会执行呢?同样以之前定义的用语读取/显示文章内容的异步Action为例,我们按照如下的方式将定义在ArticleAsync方法中针对AsyncManager的OutstandingOperations属性的Increment和Decrement方法调用注释调用,ArticleCompleted方法是否还能正常运行呢?


[code][code] 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);

}

}

[/code]
[/code]
实际上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方法的定义是不对的。


[code][code] public class HomeController : AsyncController

{

 public void XxxAsync(string name)

{

Task.Factory.StartNew(() =>

{

 AsyncManager.OutstandingOperations.Increment();

 //...

AsyncManager.OutstandingOperations.Decrement();

});

}

 //其他成员

} 

[/code]
[/code]
下面采用正确的定义方法:


[code][code] public class HomeController : AsyncController

{

 public void XxxAsync(string name)

{

AsyncManager.OutstandingOperations.Increment();

Task.Factory.StartNew(() =>

{

 //...

AsyncManager.OutstandingOperations.Decrement();

});

}

 //其他成员

}

[/code]
[/code]
最后再强调一点,不论是显式调用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异常发生。


[code][code] public class HomeController : AsyncController

{

 public Task<ActionResult> Data()

{

returnTask.Factory.StartNew(() =>

{

 while (true)

{}

 return GetModel();

 

}).ContinueWith<ActionResult>(task =>

{

 object model = task.Result;

 return View(task.Result);

});

}

 //其他成员

}

[/code]
[/code]
在ASP.NET MVC应用编程接口中具有两个特殊的特性用于定制异步操作执行的超时时限,它们是具有如下定义的AsyncTimeoutAttribute和NoAsyncTimeoutAttribute,均定义在命名空间System.Web.Mvc下。

[code]
[code][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)

{

}

}

[/code]
[/code]
从上面给出的定义我们可以看出这两个特性均是ActionFilter。AsyncTimeoutAttribute的构造函数接受一个表示超时时限(以毫秒为单位)的整数作为其参数,它通过重写OnActionExecuting方法将指定的超时时限设置给当前Controller的AsyncManager的Timeout属性进行。NoAsyncTimeoutAttribute是AsyncTimeoutAttribute的继承者,它将超时时限设置为-1,意味着它解除了对超时的限制。

从应用在这两个特性的AttributeUsageAttribute定义可看出,它们既可以应用于类也可以用于也方法,意味着我们可以将它们应用到Controller类型或者异步Action方法(仅对XxxAsync方法有效,不能应用到XxxCompleted方法上)。如果我们将它们同时应用到Controller类和Action方法上,针对方法级别的特性无疑具有更高的优先级。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: