Detailed description of the definition and execution principle of asynchronous Action under ASP. net mvc, asp. netmvc
The Controller creation Wizard provided by Visual Studio creates a Controller type inherited from the abstract class Controller by default. Such a Controller can only define synchronous Action methods. To define an asynchronous Action method, we must inherit the abstract class AsyncController. This article describes two different asynchronous Action definition methods and the underlying execution principle.
I. Thread Pool-based request processing
ASP. NET processes concurrent HTTP requests through the thread pool mechanism. A Web application maintains a thread pool. When a request to this application is detected, an idle thread is obtained from the pool to process the request. When the processing is completed, the thread will not be recycled, but will be released to the pool again. The thread pool has the maximum capacity of a thread. If the created thread reaches the upper limit and all threads are in the "busy" status, the new HTTP request is put into a request queue, waiting for a thread that completes the request processing task to be released to the pool again.
The threads used to process HTTP requests are called Worker threads, which are naturally called Worker Thread pools. ASP. NET, a thread pool-based request processing mechanism, has the following two advantages:
- Reuse of working threads: although the thread creation cost is not as good as the process activation, it is not a "one-stop" task. Frequent creation and release of threads will cause great performance damage. The thread pool mechanism avoids creating new working threads to process every request. The created working thread is greatly reused, and the server throughput is improved.
- Restrictions on the number of worker threads: limited resources have an upper limit on the server's ability to process requests, or a critical point for the number of concurrent requests that a server can process. Once the threshold is exceeded, the whole service will crash because it cannot provide enough resources. ASP. net mvc concurrent processing requests cannot exceed the maximum allowed capacity of the thread pool, thus avoiding the unlimited creation of working threads in high concurrency, which leads to the overall server crash.
If the request processing operation takes a short time, the worker thread can be promptly released to the thread pool to process the next request. However, for time-consuming operations, it means that the worker thread will be monopolized by a request for a long time. If such operations are frequently accessed, in the case of high concurrency, it means that idle working threads may not be found in the thread pool for timely processing of the latest arrival requests.
If we use an asynchronous method to process such time-consuming requests, the working thread can let the background thread take over and can be promptly released to the thread pool for subsequent request processing, this improves the throughput of the entire server. It is worth mentioning that asynchronous operations are mainly used for I/O Binding operations (such as database access and remote service calls), rather than CPU Binding operations, this is because the overall performance improvement of asynchronous operations comes from: When an I/O device is processing a task, the CPU can be released to process another task. If time-consuming operations are mainly dependent on local CPU operations, the asynchronous method will affect the overall performance due to thread scheduling and context switching.
Ii. Definitions of two asynchronous Action Methods
After understanding the necessity of defining asynchronous Action methods in AsyncController, let's briefly introduce the definition method of asynchronous Action methods. In general, asynchronous Action methods have two defining methods. One is to define them as two matching methods: XxxAsync/XxxCompleted, the other is to define a method with the return type of Task.
XxxAsync/XxxCompleted
If we use two matching methods XxxAsync/XxxCompleted to define asynchronous actions, we can implement asynchronous operations in the XxxAsync method, and implement the final content presentation in the XxxCompleted method. XxxCompleted can be viewed as a callback for XxxAsync. When the operation defined in the XxxAsync method is executed asynchronously, The XxxCompleted method is automatically called. The XxxCompleted definition method is similar to the normal synchronous Action method.
As a demonstration, I defined an asynchronous operation named Article in the next HomeController to present the content of the Article with the specified name. The asynchronous reading of the specified content is defined in the ArticleAsync method, and the content read in the ArticleCompleted method is displayed as 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); } }
For Asynchronous Action methods defined in the form of XxxAsync/XxxCompleted, ASP. net mvc does not call the XxxAsync method asynchronously, so we need to customize the implementation of asynchronous operations in this method. In the ArticleAsync method defined above, we use the Task-based parallel programming method to Implement Asynchronous reading of the article content. When we define an asynchronous Action Method in the form of XxxAsync/XxxCompleted, The AsyncManager attribute of the Controller is frequently used. This attribute returns an AsyncManager object of the type, we will discuss it separately in the following section.
In the instance provided above, we call the Increment and Decrement methods of the OutstandingOperations attribute of AsyncManager to initiate notifications for ASP. net mvc at the beginning and end of asynchronous operations. In addition, we also use the dictionary represented by the Parameters attribute of AsyncManager to save the Parameters passed to the ArticleCompleted method. The parameter Key (content) in the dictionary matches the parameter name of ArticleCompleted, therefore, when the ArticleCompleted method is called, the parameter value specified through the Parameters attribute of AsyncManager is automatically used as the corresponding parameter value.
Task Return Value
If we use the asynchronous Action definition method above, we have to define two methods for an Action. In fact, we can use one method to define an asynchronous Action, that is, let the Action method return a Task object that represents asynchronous operations. The asynchronous Action defined above in the form of XxxAsync/XxxCompleted can be defined as follows.
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); }); } }
The return type of the asynchronous Action method Article defined above is Task <ActionResult>. We read the asynchronous file content in the returned Task object. The callback operation for File Content Rendering registers by calling the ContinueWith <ActionResult> method of the Task object. This operation is automatically called after the asynchronous operation is completed.
As shown in the code snippet above, we still use the Parameters attribute of AsyncManager to transfer Parameters between asynchronous operations and callback operations. In fact, we can also use the Result attribute of the Task object to implement the same function. The definition of the Article method is also rewritten as follows.
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); }); } }
Iii. AsyncManager
In the definition of asynchronous Action demonstrated above, we use AsyncManager to implement two basic functions: Passing parameters between asynchronous operations and callback operations and sending them to ASP. net mvc sends notifications about the start and end of asynchronous operations. Because AsyncManager plays an important role in asynchronous Action scenarios, it is necessary to introduce it separately. The following is the definition of 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; } }
As shown in the code snippet above, AsyncManager has two constructor loads. non-default constructor accepts a SynchronizationContext object that represents the synchronization context as a parameter. If the specified synchronization context object is Null and the Current synchronization context (expressed by the static attribute Current of SynchronizationContext) exists, this context is used; otherwise, a new synchronization context is created. The synchronization context is used to execute the Sync method. That is to say, the Action Delegate specified by the method will be executed in synchronous mode in the synchronization context.
The core of AsyncManager is the ongoing Asynchronous Operation counter indicated by the OutstandingOperations attribute, which is an object of the OperationCounter type. The operation Count is represented by the read-only attribute Count. When an asynchronous operation is started and completed, the Increment and Decrement methods are called to add and describe the counting Operations respectively. Both Increment and Decrement have two overloading methods. As an integer parameter value (the value can be a negative number), the value is an added or subtracted value. If no parameter method is called, the increase or decrease value is 1. If we need to execute multiple asynchronous operations at the same time, we can use the following method to operate the counter.
AsyncManager. outstandingOperations. increment (3); Task. factory. startNew () => {// Asynchronous Operation 1 AsyncManager. outstandingOperations. decrement () ;}); Task. factory. startNew () => {// Asynchronous Operation 2 AsyncManager. outstandingOperations. decrement () ;}); Task. factory. startNew () => {// Asynchronous Operation 3 AsyncManager. outstandingOperations. decrement ();});
OperationCounter object checks whether the current Count value is zero for every change in the Count value caused by calling the Increment and Decrement methods. If so, all operations are completed, if a Completed event is registered in advance, the event is triggered. It is worth mentioning that the result of all operations indicates that the counter value is equal to zero rather than less than zero. If we call the Increment and Decrement methods to call the counter value as a negative number, the registered Completed event will not be triggered.
AsyncManager registers the Completed event of the OperationCounter object indicated by OutstandingOperations during initialization, so that it calls its own Finish method when triggered. The default Implementation of Virtual method Finish in AsyncManager triggers its Finished event.
As shown in the following code snippet, the Controller class implements the IAsyncManagerContainer interface, while the latter defines an AsyncManager object that is read-only and used to assist in asynchronous Action execution, the AsyncManager object we use to define the asynchronous Action method is the AsyncManager attribute integrated from the abstract class Controller.
public abstract class Controller ControllerBase, IAsyncManagerContainer,... { public AsyncManager AsyncManager { get; } } public interface IAsyncManagerContainer { AsyncManager AsyncManager { get; } }
Iv. Execution of the Completed Method
For Asynchronous actions defined in the form of XxxAsync/XxxCompleted, we say that the callback operation XxxCompleted will be automatically called after the asynchronous operation defined in the XxxAsync method is completed, so how is the XxxCompleted method executed?
Asynchronous Action execution is completed by describing the BeginExecute/EndExecute method of the AsyncActionDescriptor object of the Action. Through the introduction of "Model binding", we know that asynchronous actions defined in the form of XxxAsync/XxxCompleted are represented by a ReflectedAsyncActionDescriptor object, reflectedAsyncActionDescriptor registers the AsyncManager Finished event of the Controller object when executing the BeginExecute method, so that the Completed method is executed when the event is triggered.
That is to say, the Finished event for the current Controller's AsyncManager indicates the end of the asynchronous operation, and the matched Completed method will be executed. Because AsyncManager's Finish method will trigger this event, we can call this method to execute the Completed method immediately. The Finish method is called when the Completed event of the OperationCounter object of AsyncManager is triggered. Therefore, when the value of the currently executed Asynchronous Operation calculator is zero, the Completed method is automatically executed.
If we execute three asynchronous operations simultaneously in the XxxAsync method in the following way, and call the AsyncManager Finish method after each operation is completed, this means that the first asynchronous operation will lead to the execution of the XxxCompleted method. In other words, when the XxxCompleted method is executed, there may be two asynchronous operations in progress.
AsyncManager. outstandingOperations. increment (3); Task. factory. startNew () => {// Asynchronous Operation 1 AsyncManager. finish () ;}); Task. factory. startNew () => {// Asynchronous Operation 2 AsyncManager. finish () ;}); Task. factory. startNew () => {// Asynchronous Operation 3 AsyncManager. finish ();});
If the XxxCompleted method is fully controlled by the Completed Asynchronous Operation counting mechanism, the Count Detection and Completed event trigger only occurs when the Increment/Decrement Method of OperationCounter is executed, if neither of the two methods is called at the beginning or end of the asynchronous operation, will XxxCompleted be executed? Take the asynchronous Action of reading/displaying the content of an article as an example, in the ArticleAsync method, we define the Increment and Decrement methods for the OutstandingOperations attribute of AsyncManager to call annotation calls. Can the ArticleCompleted method still run normally?
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); } }
In fact, ArticleCompleted will still be executed, but we cannot ensure that the article content is read normally, because the ArticleCompleted method will be executed immediately after the ArticleAsync method is executed. If reading the article content is a relatively time-consuming operation, it indicates that the content parameter of the ArticleCompleted method of the article content has not been initialized during execution. In this case, how is ArticleCompleted executed?
Cause and Simplicity: The BeginExecute method of ReflectedAsyncActionDescriptor calls the Increment and Decrement methods of the OutstandingOperations attribute of AsyncManager before and after the XxxAsync method is executed. For the example given, before executing ArticleAsync, the Increment method is called to change the value of the calculator to 1, and then ArticleAsync is executed. Because this method asynchronously reads the specified file content, so it will return immediately. Finally, the Decrement method is executed to change the counter value to 0. The AsyncManager Completed event is triggered and the ArticleCompleted method is executed. At this time, the file content is being read, indicating that the content parameter of the article content is not initialized yet.
The execution mechanism such as ReflectedAsyncActionDescriptor also puts forward requirements for our use of AsyncManager, that is, adding operations to the unfinished one-step operation counter should not occur in the asynchronous thread, the following is an incorrect definition of the Increment method for the OutstandingOperations attribute of AsyncManager.
Public class HomeController AsyncController {public void XxxAsync (string name) {Task. factory. startNew () => {AsyncManager. outstandingOperations. increment ();//... asyncManager. outstandingOperations. decrement () ;}) ;}// other members}
The following uses the correct definition method:
Public class HomeController AsyncController {public void XxxAsync (string name) {AsyncManager. outstandingOperations. increment (); Task. factory. startNew () => {//... asyncManager. outstandingOperations. decrement () ;}) ;}// other members}
In the end, whether it is to explicitly call the Finish method of AsyncManager or call the Increment method of the OutstandingOperations attribute of AsyncManager, the counter value is changed to zero, which only allows the XxxCompleted method to be executed, it does not really block the execution of asynchronous operations.
5. Asynchronous Operation timeout Control
Although asynchronous operations are suitable for those I/O Binding operations that are relatively time-consuming, there is no limit on the execution time of one-step operations. The asynchronous Timeout period is expressed by the integer attribute Timeout of AsyncManager, which indicates the total number of milliseconds of the Timeout period. The default value is 45000 (45 seconds ). If the Timeout attribute is set to-1, it means that the asynchronous operation does not have any time limit. For Asynchronous actions defined in the form of XxxAsync/XxxCompleted, if XxxCompleted is not executed in the specified timeout period after XxxAsync is executed, a TimeoutException will be thrown out.
If we define an asynchronous Action in the form of a Task with the return type, the execution time of the Asynchronous Operation reflected by the Task is not limited by the Timeout attribute of AsyncManager. The following code defines an asynchronous Action method named Data to obtain Data as a Model in asynchronous mode and present the Data in the default View. However, an infinite loop exists in asynchronous operations, when we access this Data method, asynchronous operations will be executed without limit, and there will be no TimeoutException exception.
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) ;}) ;}// other members}
In ASP. net mvc application programming interface has two special features used to customize the time-out period for Asynchronous Operation execution. They are defined as AsyncTimeoutAttribute and NoAsyncTimeoutAttribute, which are defined in the namespace System. web. under 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) { } }
From the above definition, we can see that these two features are ActionFilter. The constructor of AsyncTimeoutAttribute takes an integer representing the timeout period (in milliseconds) as its parameter, it uses the override OnActionExecuting method to set the specified Timeout period to the Timeout attribute of the AsyncManager of the current Controller. NoAsyncTimeoutAttribute is the successor of AsyncTimeoutAttribute. It sets the timeout period to-1, which means it removes the timeout limit.
From the AttributeUsageAttribute definitions of the application in these two features, we can see that they can be applied to both classes and methods, this means that we can apply them to the Controller type or asynchronous Action method (only valid for XxxAsync methods, not XxxCompleted methods ). If we apply them to the Controller class and Action method at the same time, the method-level features will undoubtedly have a higher priority.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.