Visual Studio提供的Controller創(chuàng)建向?qū)J(rèn)為我們創(chuàng)建一個(gè)繼承自抽象類Controller的Controller類型,這樣的Controller只能定義同步Action方法。如果我們需要定義異步Action方法,必須繼承抽象類AsyncController。這篇問你講述兩種不同的異步Action的定義方法和底層執(zhí)行原理。
一、基于線程池的請(qǐng)求處理
ASP.NET通過線程池的機(jī)制處理并發(fā)的HTTP請(qǐng)求。一個(gè)Web應(yīng)用內(nèi)部維護(hù)著一個(gè)線程池,當(dāng)探測(cè)到抵達(dá)的針對(duì)本應(yīng)用的請(qǐng)求時(shí),會(huì)從池中獲取一個(gè)空閑的線程來處理該請(qǐng)求。當(dāng)處理完畢,線程不會(huì)被回收,而是重新釋放到池中。線程池具有一個(gè)線程的最大容量,如果創(chuàng)建的線程達(dá)到這個(gè)上限并且所有的線程均被處于“忙碌”狀態(tài),新的HTTP請(qǐng)求會(huì)被放入一個(gè)請(qǐng)求隊(duì)列以等待某個(gè)完成了請(qǐng)求處理任務(wù)的線程重新釋放到池中。
我們將這些用于處理HTTP請(qǐng)求的線程稱為工作線程(Worker Thread),而這個(gè)縣城池自然就叫做工作線程池。ASP.NET這種基于線程池的請(qǐng)求處理機(jī)制主要具有如下兩個(gè)優(yōu)勢(shì):
- 工作線程的重用:創(chuàng)建線程的成本雖然不如進(jìn)程的激活,卻也不是一件“一蹴而就”的事情,頻繁地創(chuàng)建和釋放線程會(huì)對(duì)性能造成極大的損害。而線程池機(jī)制避免了總是創(chuàng)建新的工作線程來處理每一個(gè)請(qǐng)求,被創(chuàng)建的工作線程得到了極大地重用,并最終提高了服務(wù)器的吞吐能力。
- 工作線程數(shù)量的限制:資源的有限性具有了服務(wù)器處理請(qǐng)求的能力具有一個(gè)上限,或者說某臺(tái)服務(wù)器能夠處理的請(qǐng)求并發(fā)量具有一個(gè)臨界點(diǎn),一旦超過這個(gè)臨界點(diǎn),整臺(tái)服務(wù)將會(huì)因不能提供足夠的資源而崩潰。由于采用了對(duì)工作線程數(shù)量具有良好控制的線程池機(jī)制,ASP.NET MVC并發(fā)處理的請(qǐng)求數(shù)量不可能超過線程池的最大允許的容量,從而避免了在高并發(fā)情況下工作線程的無限制創(chuàng)建而最導(dǎo)致整個(gè)服務(wù)器的崩潰。
如果請(qǐng)求處理操作耗時(shí)較短,那么工作線程處理完畢后可以及時(shí)地被釋放到線程池中以用于對(duì)下一個(gè)請(qǐng)求的處理。但是對(duì)于比較耗時(shí)的操作來說,意味著工作線程將被長時(shí)間被某個(gè)請(qǐng)求獨(dú)占,如果這樣的操作訪問比較頻繁,在高并發(fā)的情況下意味著線程池中將可能找不到空閑的工作線程用于及時(shí)處理最新抵達(dá)請(qǐng)求。
如果我們采用異步的方式來處理這樣的耗時(shí)請(qǐng)求,工作線程可以讓后臺(tái)線程來接手,自己可以及時(shí)地被釋放到線程池中用于進(jìn)行后續(xù)請(qǐng)求的處理,從而提高了整個(gè)服務(wù)器的吞吐能力。值得一提的是,異步操作主要用于I/O綁定操作(比如數(shù)據(jù)庫訪問和遠(yuǎn)程服務(wù)調(diào)用等),而非CPU綁定操作,因?yàn)楫惒讲僮鲗?duì)整體性能的提升來源于:當(dāng)I/O設(shè)備在處理某個(gè)任務(wù)的時(shí)候,CPU可以釋放出來處理另一個(gè)任務(wù)。如果耗時(shí)操作主要依賴于本機(jī)CPU的運(yùn)算,采用異步方法反而會(huì)因?yàn)榫€程調(diào)度和線程上下文的切換而影響整體的性能。
二、兩種異步Action方法的定義
在了解了在AsyncController中定義異步Action方法的必要性之后,我們來簡單介紹一下異步Action方法的定義方式。總的來說,異步Action方法具有兩種定義方式,一種是將其定義成兩個(gè)匹配的方法XxxAsync/XxxCompleted,另一種則是定義一個(gè)返回類型為Task的方法。
XxxAsync/XxxCompleted
如果我們使用兩個(gè)匹配的方法XxxAsync/XxxCompleted來定義異步Action,我們可以將異步操作實(shí)現(xiàn)在XxxAsync方法中,而將最終內(nèi)容的呈現(xiàn)實(shí)現(xiàn)在XxxCompleted方法中。XxxCompleted可以看成是針對(duì)XxxAsync的回調(diào),當(dāng)定義在XxxAsync方法中的操作以異步方式執(zhí)行完成后,XxxCompleted方法會(huì)被自動(dòng)調(diào)用。XxxCompleted的定義方式和普通的同步Action方法比較類似。
作為演示,我在如下一個(gè)HomeController中定義了一個(gè)名為Article的異步操作來呈現(xiàn)指定名稱的文章內(nèi)容。我們將指定文章內(nèi)容的異步讀取定義在ArticleAsync方法中,而在ArticleCompleted方法中講讀取的內(nèi)容以ContentResult的形式呈現(xiàn)出來。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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); } } |
對(duì)于以XxxAsync/XxxCompleted形式定義的異步Action方法來說,ASP.NET MVC并不會(huì)以異步的方式來調(diào)用XxxAsync方法,所以我們需要在該方法中自定義實(shí)現(xiàn)異步操作的執(zhí)行。在上面定義的ArticleAsync方法中,我們是通過基于Task的并行編程方式來實(shí)現(xiàn)對(duì)文章內(nèi)容的異步讀取的。當(dāng)我們以XxxAsync/XxxCompleted形式定義的異步Action方法的時(shí)候,會(huì)頻繁地使用到Controller的AsyncManager屬性,該屬性返回一個(gè)類型為AsyncManager對(duì)象,我們將在下面一節(jié)對(duì)其進(jìn)行單獨(dú)講述。
在上面提供的實(shí)例中,我們?cè)诋惒讲僮鏖_始和結(jié)束的時(shí)候調(diào)用了AsyncManager的OutstandingOperations屬性的Increment和Decrement方法對(duì)于ASP.NET MVC發(fā)起通知。此外,我們還利用AsyncManager的Parameters屬性表示的字典來保存?zhèn)鬟f給ArticleCompleted方法的參數(shù),參數(shù)在字典中的Key(content)與ArticleCompleted的參數(shù)名稱是匹配的,所以在調(diào)用方法ArticleCompleted的時(shí)候,通過AsyncManager的Parameters屬性指定的參數(shù)值將自動(dòng)作為對(duì)應(yīng)的參數(shù)值。
Task返回值
如果采用上面的異步Action定義方式,意味著我們不得不為一個(gè)Action定義兩個(gè)方法,實(shí)際上我們可以通過一個(gè)方法來完成對(duì)異步Action的定義,那就是讓Action方法返回一個(gè)代表異步操作的Task對(duì)象。上面通過XxxAsync/XxxCompleted形式定義的異步Action可以采用如下的定義方式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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>,我們將異步文件內(nèi)容的讀取體現(xiàn)在返回的Task對(duì)象中。對(duì)文件內(nèi)容呈現(xiàn)的回調(diào)操作則通過調(diào)用該Task對(duì)象的ContinueWith<ActionResult>方法進(jìn)行注冊(cè),該操作會(huì)在異步操作完成之后被自動(dòng)調(diào)用。
如上面的代碼片斷所示,我們依然利用AsyncManager的Parameters屬性實(shí)現(xiàn)參數(shù)在異步操作和回調(diào)操作之間的傳遞。其實(shí)我們也可以使用Task對(duì)象的Result屬性來實(shí)現(xiàn)相同的功能,Article方法的定義也改寫成如下的形式。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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實(shí)現(xiàn)了兩個(gè)基本的功能,即在異步操作和回調(diào)操作之間傳遞參數(shù)和向ASP.NET MVC發(fā)送異步操作開始和結(jié)束的通知。由于AsyncManager在異步Action場(chǎng)景中具有重要的作用,我們有必要對(duì)其進(jìn)行單獨(dú)介紹,下面是AsyncManager的定義。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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具有兩個(gè)構(gòu)造函數(shù)重載,非默認(rèn)構(gòu)造函數(shù)接受一個(gè)表示同步上下文的SynchronizationContext對(duì)象作為參數(shù)。如果指定的同步上下文對(duì)象為Null,并且當(dāng)前的同步上下文(通過SynchronizationContext的靜態(tài)屬性Current表示)存在,則使用該上下文;否則創(chuàng)建一個(gè)新的同步上下文。該同步上下文用于Sync方法的執(zhí)行,也就是說在該方法指定的Action委托將會(huì)在該同步上下文中以同步的方式執(zhí)行。
AsyncManager的核心是通過屬性O(shè)utstandingOperations表示的正在進(jìn)行的異步操作計(jì)數(shù)器,該屬性是一個(gè)類型為OperationCounter的對(duì)象。操作計(jì)數(shù)通過只讀屬性Count表示,當(dāng)我們開始和完成異步操作的時(shí)候分別調(diào)用Increment和Decrement方法作增加和介紹計(jì)數(shù)操作。Increment和Decrement各自具有兩個(gè)重載,作為整數(shù)參數(shù)value(該參數(shù)值可以是負(fù)數(shù))表示增加或者減少的數(shù)值,如果調(diào)用無參方法,增加或者減少的數(shù)值為1。如果我們需要同時(shí)執(zhí)行多個(gè)異步操作,則可以通過如下的方法來操作計(jì)數(shù)器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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(); }); |
對(duì)于每次通過Increment和Decrement方法調(diào)用引起的計(jì)數(shù)數(shù)值的改變,OperationCounter對(duì)象都會(huì)檢驗(yàn)當(dāng)前計(jì)數(shù)數(shù)值是否為零,如果則表明所有的操作運(yùn)行完畢,如果預(yù)先注冊(cè)了Completed事件,該事件會(huì)被觸發(fā)。值得一提的時(shí)候,表明所有操作完成執(zhí)行的標(biāo)志是計(jì)數(shù)器的值等于零,而不是小于零,如果我們通過調(diào)用Increment和Decrement方法使計(jì)數(shù)器的值稱為一個(gè)負(fù)數(shù),注冊(cè)的Completed事件是不會(huì)被觸發(fā)的。
AsyncManager在初始化的時(shí)候就注冊(cè)了通過屬性O(shè)utstandingOperations表示的OperationCounter對(duì)象的Completed事件,使該事件觸發(fā)的時(shí)候調(diào)用自身的Finish方法。而虛方法Finish在AsyncManager中的默認(rèn)實(shí)現(xiàn)又會(huì)觸發(fā)自身的Finished事件。
如下面的代碼片斷所示,Controller類實(shí)現(xiàn)了IAsyncManagerContainer接口,而后者定義了一個(gè)只讀屬性AsyncManager用于提供輔助執(zhí)行異步Action的AsyncManager對(duì)象,而我們?cè)诙x異步Action方法是使用的AsyncManager對(duì)象就是從抽象類Controller中集成下來的AsyncManager屬性。
1
2
3
4
5
6
7
8
9
|
public abstract class Controller ControllerBase, IAsyncManagerContainer,... { public AsyncManager AsyncManager { get ; } } public interface IAsyncManagerContainer { AsyncManager AsyncManager { get ; } } |
四、Completed方法的執(zhí)行
對(duì)于通過XxxAsync/XxxCompleted形式定義的異步Action,我們說回調(diào)操作XxxCompleted會(huì)在定義在XxxAsync方法中的異步操作執(zhí)行結(jié)束之后被自動(dòng)調(diào)用,那么XxxCompleted方法具體是如何被執(zhí)行的呢?
異步Action的執(zhí)行最終是通過描述該Action的AsyncActionDescriptor對(duì)象的BeginExecute/EndExecute方法來完成的。通過之前“Model的綁定”的介紹我們知道通過XxxAsync/XxxCompleted形式定義的異步Action通過一個(gè)ReflectedAsyncActionDescriptor對(duì)象來表示的,ReflectedAsyncActionDescriptor在執(zhí)行BeginExecute方法的時(shí)候會(huì)注冊(cè)Controller對(duì)象的AsyncManager的Finished事件,使該事件觸發(fā)的時(shí)候去執(zhí)行Completed方法。
也就是說針對(duì)當(dāng)前Controller的AsyncManager的Finished事件的觸發(fā)標(biāo)志著異步操作的結(jié)束,而此時(shí)匹配的Completed方法會(huì)被執(zhí)行。由于AsyncManager的Finish方法會(huì)主動(dòng)觸發(fā)該事件,所以我們可以通過調(diào)用該方法使Completed方法立即執(zhí)行。由于AsyncManager的OperationCounter對(duì)象的Completed事件觸發(fā)的時(shí)候會(huì)調(diào)用Finish方法,所以當(dāng)表示當(dāng)前正在執(zhí)行的異步操作計(jì)算器的值為零時(shí),Completed方法也會(huì)自動(dòng)被執(zhí)行。
如果我們?cè)赬xxAsync方法中通過如下的方式同時(shí)執(zhí)行三個(gè)異步操作,并在每個(gè)操作完成之后調(diào)用AsyncManager的Finish方法,意味著最先完成的異步操作會(huì)導(dǎo)致XxxCompleted方法的執(zhí)行。換句話說,當(dāng)XxxCompleted方法執(zhí)行的時(shí)候,可能還有兩個(gè)異步操作正在執(zhí)行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
AsyncManager.OutstandingOperations.Increment(3); Task.Factory.StartNew(() => { //異步操作1 AsyncManager.Finish(); }); Task.Factory.StartNew(() => { //異步操作2 AsyncManager.Finish(); }); Task.Factory.StartNew(() => { //異步操作3 AsyncManager.Finish(); }); |
如果完全通過為完成的異步操作計(jì)數(shù)機(jī)制來控制XxxCompleted方法的執(zhí)行,由于計(jì)數(shù)的檢測(cè)和Completed事件的觸發(fā)只發(fā)生在OperationCounter的Increment/Decrement方法被執(zhí)行的時(shí)候,如果我們?cè)陂_始和結(jié)束異步操作的時(shí)候都沒有調(diào)用這兩個(gè)方法,XxxCompleted是否會(huì)執(zhí)行呢?同樣以之前定義的用語讀取/顯示文章內(nèi)容的異步Action為例,我們按照如下的方式將定義在ArticleAsync方法中針對(duì)AsyncManager的OutstandingOperations屬性的Increment和Decrement方法調(diào)用注釋調(diào)用,ArticleCompleted方法是否還能正常運(yùn)行呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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); } } |
實(shí)際上ArticleCompleted依然會(huì)被執(zhí)行,但是這樣我們就不能確保正常讀取文章內(nèi)容,因?yàn)锳rticleCompleted方法會(huì)在ArticleAsync方法執(zhí)行之后被立即執(zhí)行。如果文章內(nèi)容讀取是一個(gè)相對(duì)耗時(shí)的操作,表示文章內(nèi)容的ArticleCompleted方法的content參數(shù)在執(zhí)行的時(shí)候尚未被初始化。在這種情況下的ArticleCompleted是如何被執(zhí)行的呢?
原因和簡單,ReflectedAsyncActionDescriptor的BeginExecute方法在執(zhí)行XxxAsync方法的前后會(huì)分別調(diào)用AsyncManager的OutstandingOperations屬性的Increment和Decrement方法。對(duì)于我們給出的例子來說,在執(zhí)行ArticleAsync之前Increment方法被調(diào)用使計(jì)算器的值變成1,隨后ArticleAsync被執(zhí)行,由于該方法以異步的方式讀取指定的文件內(nèi)容,所以會(huì)立即返回。最后Decrement方法被執(zhí)行使計(jì)數(shù)器的值變成0,AsyncManager的Completed事件被觸發(fā)并導(dǎo)致ArticleCompleted方法的執(zhí)行。而此時(shí),文件內(nèi)容的讀取正在進(jìn)行之中,表示文章內(nèi)容的content參數(shù)自然尚未被初始化。
ReflectedAsyncActionDescriptor這樣的執(zhí)行機(jī)制也對(duì)我們使用AsyncManager提出了要求,那就是對(duì)尚未完成的一步操作計(jì)數(shù)器的增加操作不應(yīng)該發(fā)生在異步線程中,如下所示的針對(duì)AsyncManager的OutstandingOperations屬性的Increment方法的定義是不對(duì)的。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class HomeController AsyncController { public void XxxAsync( string name) { Task.Factory.StartNew(() => { AsyncManager.OutstandingOperations.Increment(); //... AsyncManager.OutstandingOperations.Decrement(); }); } //其他成員 } |
下面采用正確的定義方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class HomeController AsyncController { public void XxxAsync( string name) { AsyncManager.OutstandingOperations.Increment(); Task.Factory.StartNew(() => { //... AsyncManager.OutstandingOperations.Decrement(); }); } //其他成員 } |
最后再強(qiáng)調(diào)一點(diǎn),不論是顯式調(diào)用AsyncManager的Finish方法,還是通過調(diào)用AsyncManager的OutstandingOperations屬性的Increment方法是計(jì)數(shù)器的值變成零,僅僅是讓XxxCompleted方法得以執(zhí)行,并不能真正阻止異步操作的執(zhí)行。
五、異步操作的超時(shí)控制
異步操作雖然適合那些相對(duì)耗時(shí)的I/O綁定型操作,但是也并不說對(duì)一步操作執(zhí)行的時(shí)間沒有限制。異步超時(shí)時(shí)限通過AsyncManager的整型屬性Timeout表示,它表示超時(shí)時(shí)限的總毫秒數(shù),其默認(rèn)值為45000(45秒)。如果將Timeout屬性設(shè)置為-1,意味著異步操作執(zhí)行不再具有任何時(shí)間的限制。對(duì)于以XxxAsync/XxxCompleted形式定義的異步Action來說,如果XxxAsync執(zhí)行之后,在規(guī)定的超時(shí)時(shí)限中XxxCompleted沒有得到執(zhí)行,一個(gè)TimeoutException會(huì)被拋出來。
如果我們以返回類型為Task的形式定義異步Action,通過Task體現(xiàn)的異步操作的執(zhí)行時(shí)間不受AsyncManager的Timeout屬性的限制。我們通過如下的代碼定義了一個(gè)名為Data的異步Action方法以異步的方式獲取作為Model的數(shù)據(jù)并通過默認(rèn)的View呈現(xiàn)出來,但是異步操作中具有一個(gè)無限循環(huán),當(dāng)我們?cè)L問該Data方法時(shí),異步操作將會(huì)無限制地執(zhí)行下去,也不會(huì)有TimeoutException異常發(fā)生。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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應(yīng)用編程接口中具有兩個(gè)特殊的特性用于定制異步操作執(zhí)行的超時(shí)時(shí)限,它們是具有如下定義的AsyncTimeoutAttribute和NoAsyncTimeoutAttribute,均定義在命名空間System.Web.Mvc下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
[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) { } } |
從上面給出的定義我們可以看出這兩個(gè)特性均是ActionFilter。AsyncTimeoutAttribute的構(gòu)造函數(shù)接受一個(gè)表示超時(shí)時(shí)限(以毫秒為單位)的整數(shù)作為其參數(shù),它通過重寫OnActionExecuting方法將指定的超時(shí)時(shí)限設(shè)置給當(dāng)前Controller的AsyncManager的Timeout屬性進(jìn)行。NoAsyncTimeoutAttribute是AsyncTimeoutAttribute的繼承者,它將超時(shí)時(shí)限設(shè)置為-1,意味著它解除了對(duì)超時(shí)的限制。
從應(yīng)用在這兩個(gè)特性的AttributeUsageAttribute定義可看出,它們既可以應(yīng)用于類也可以用于也方法,意味著我們可以將它們應(yīng)用到Controller類型或者異步Action方法(僅對(duì)XxxAsync方法有效,不能應(yīng)用到XxxCompleted方法上)。如果我們將它們同時(shí)應(yīng)用到Controller類和Action方法上,針對(duì)方法級(jí)別的特性無疑具有更高的優(yōu)先級(jí)。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/artech/archive/2012/06/20/async-action-in-mvc.html