Lazy create MVC ControllerActionInvoker statemachines (#9533)

* Lazy create ControllerActionInvoker.InvokeInnerFilterAsync statemachine

* Lazy create ControllerActionInvoker.InvokeNextActionFilterAwaitedAsync statemachine

* Lazy create ControllerActionInvoker.InvokeNextActionFilterAsync statemachine

* Lazy create ControllerActionInvoker.InvokeActionMethodAsync statemachine

* Lazy create Controller.OnActionExecutionAsync statemachine

* Ref assembly

* Formatting feedback
This commit is contained in:
Ben Adams 2019-04-19 15:48:26 +01:00 committed by Ryan Nowak
parent a1d2d64120
commit d46388b58e
3 changed files with 167 additions and 56 deletions

View File

@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
} }
} }
private async Task InvokeNextActionFilterAsync() private Task InvokeNextActionFilterAsync()
{ {
try try
{ {
@ -292,7 +292,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
var isCompleted = false; var isCompleted = false;
while (!isCompleted) while (!isCompleted)
{ {
await Next(ref next, ref scope, ref state, ref isCompleted); var lastTask = Next(ref next, ref scope, ref state, ref isCompleted);
if (!lastTask.IsCompletedSuccessfully)
{
return Awaited(this, lastTask, next, scope, state, isCompleted);
}
} }
} }
catch (Exception exception) catch (Exception exception)
@ -304,14 +308,59 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
} }
Debug.Assert(_actionExecutedContext != null); Debug.Assert(_actionExecutedContext != null);
return Task.CompletedTask;
static async Task Awaited(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
{
try
{
await lastTask;
while (!isCompleted)
{
await invoker.Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
invoker._actionExecutedContext = new ActionExecutedContext(invoker._controllerContext, invoker._filters, invoker._instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
Debug.Assert(invoker._actionExecutedContext != null);
}
} }
private async Task<ActionExecutedContext> InvokeNextActionFilterAwaitedAsync() private Task<ActionExecutedContext> InvokeNextActionFilterAwaitedAsync()
{ {
Debug.Assert(_actionExecutingContext != null); Debug.Assert(_actionExecutingContext != null);
if (_actionExecutingContext.Result != null) if (_actionExecutingContext.Result != null)
{ {
// If we get here, it means that an async filter set a result AND called next(). This is forbidden. // If we get here, it means that an async filter set a result AND called next(). This is forbidden.
return Throw();
}
var task = InvokeNextActionFilterAsync();
if (!task.IsCompletedSuccessfully)
{
return Awaited(this, task);
}
Debug.Assert(_actionExecutedContext != null);
return Task.FromResult(_actionExecutedContext);
static async Task<ActionExecutedContext> Awaited(ControllerActionInvoker invoker, Task task)
{
await task;
Debug.Assert(invoker._actionExecutedContext != null);
return invoker._actionExecutedContext;
}
#pragma warning disable CS1998
static async Task<ActionExecutedContext> Throw()
{
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit( var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
typeof(IAsyncActionFilter).Name, typeof(IAsyncActionFilter).Name,
nameof(ActionExecutingContext.Result), nameof(ActionExecutingContext.Result),
@ -320,69 +369,119 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
#pragma warning restore CS1998
await InvokeNextActionFilterAsync();
Debug.Assert(_actionExecutedContext != null);
return _actionExecutedContext;
} }
private async Task InvokeActionMethodAsync() private Task InvokeActionMethodAsync()
{ {
var controllerContext = _controllerContext; if (_diagnosticListener.IsEnabled() || _logger.IsEnabled(LogLevel.Trace))
var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
var controller = _instance;
var arguments = _arguments;
var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
var diagnosticListener = _diagnosticListener;
var logger = _logger;
IActionResult result = null;
try
{ {
diagnosticListener.BeforeActionMethod( return Logged(this);
controllerContext,
arguments,
controller);
logger.ActionMethodExecuting(controllerContext, orderedArguments);
var stopwatch = ValueStopwatch.StartNew();
var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, controller, orderedArguments);
if (actionResultValueTask.IsCompletedSuccessfully)
{
result = actionResultValueTask.Result;
}
else
{
result = await actionResultValueTask;
}
_result = result;
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
} }
finally
var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor;
var actionMethodExecutor = _cacheEntry.ActionMethodExecutor;
var orderedArguments = PrepareArguments(_arguments, objectMethodExecutor);
var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, _instance, orderedArguments);
if (actionResultValueTask.IsCompletedSuccessfully)
{ {
diagnosticListener.AfterActionMethod( _result = actionResultValueTask.Result;
controllerContext, }
arguments, else
controllerContext, {
result); return Awaited(this, actionResultValueTask);
}
return Task.CompletedTask;
static async Task Awaited(ControllerActionInvoker invoker, ValueTask<IActionResult> actionResultValueTask)
{
invoker._result = await actionResultValueTask;
}
static async Task Logged(ControllerActionInvoker invoker)
{
var controllerContext = invoker._controllerContext;
var objectMethodExecutor = invoker._cacheEntry.ObjectMethodExecutor;
var controller = invoker._instance;
var arguments = invoker._arguments;
var actionMethodExecutor = invoker._cacheEntry.ActionMethodExecutor;
var orderedArguments = PrepareArguments(arguments, objectMethodExecutor);
var diagnosticListener = invoker._diagnosticListener;
var logger = invoker._logger;
IActionResult result = null;
try
{
diagnosticListener.BeforeActionMethod(
controllerContext,
arguments,
controller);
logger.ActionMethodExecuting(controllerContext, orderedArguments);
var stopwatch = ValueStopwatch.StartNew();
var actionResultValueTask = actionMethodExecutor.Execute(invoker._mapper, objectMethodExecutor, controller, orderedArguments);
if (actionResultValueTask.IsCompletedSuccessfully)
{
result = actionResultValueTask.Result;
}
else
{
result = await actionResultValueTask;
}
invoker._result = result;
logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime());
}
finally
{
diagnosticListener.AfterActionMethod(
controllerContext,
arguments,
controllerContext,
result);
}
} }
} }
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the /// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
/// variables in this method represent.</remarks> /// variables in this method represent.</remarks>
protected override async Task InvokeInnerFilterAsync() protected override Task InvokeInnerFilterAsync()
{ {
var next = State.ActionBegin; try
var scope = Scope.Invoker;
var state = (object)null;
var isCompleted = false;
while (!isCompleted)
{ {
await Next(ref next, ref scope, ref state, ref isCompleted); var next = State.ActionBegin;
var scope = Scope.Invoker;
var state = (object)null;
var isCompleted = false;
while (!isCompleted)
{
var lastTask = Next(ref next, ref scope, ref state, ref isCompleted);
if (!lastTask.IsCompletedSuccessfully)
{
return Awaited(this, lastTask, next, scope, state, isCompleted);
}
}
return Task.CompletedTask;
}
catch (Exception ex)
{
// Wrap non task-wrapped exceptions in a Task,
// as this isn't done automatically since the method is not async.
return Task.FromException(ex);
}
static async Task Awaited(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)
{
await lastTask;
while (!isCompleted)
{
await invoker.Next(ref next, ref scope, ref state, ref isCompleted);
}
} }
} }

View File

@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Mvc
[Microsoft.AspNetCore.Mvc.NonActionAttribute] [Microsoft.AspNetCore.Mvc.NonActionAttribute]
public virtual void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { } public virtual void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { }
[Microsoft.AspNetCore.Mvc.NonActionAttribute] [Microsoft.AspNetCore.Mvc.NonActionAttribute]
[System.Diagnostics.DebuggerStepThroughAttribute]
public virtual System.Threading.Tasks.Task OnActionExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context, Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate next) { throw null; } public virtual System.Threading.Tasks.Task OnActionExecutionAsync(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context, Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate next) { throw null; }
[Microsoft.AspNetCore.Mvc.NonActionAttribute] [Microsoft.AspNetCore.Mvc.NonActionAttribute]
public virtual Microsoft.AspNetCore.Mvc.PartialViewResult PartialView() { throw null; } public virtual Microsoft.AspNetCore.Mvc.PartialViewResult PartialView() { throw null; }

View File

@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc
/// of <see cref="OnActionExecutionAsync" /> to continue execution of the action.</param> /// of <see cref="OnActionExecutionAsync" /> to continue execution of the action.</param>
/// <returns>A <see cref="Task"/> instance.</returns> /// <returns>A <see cref="Task"/> instance.</returns>
[NonAction] [NonAction]
public virtual async Task OnActionExecutionAsync( public virtual Task OnActionExecutionAsync(
ActionExecutingContext context, ActionExecutingContext context,
ActionExecutionDelegate next) ActionExecutionDelegate next)
{ {
@ -357,7 +357,20 @@ namespace Microsoft.AspNetCore.Mvc
OnActionExecuting(context); OnActionExecuting(context);
if (context.Result == null) if (context.Result == null)
{ {
OnActionExecuted(await next()); var task = next();
if (!task.IsCompletedSuccessfully)
{
return Awaited(this, task);
}
OnActionExecuted(task.Result);
}
return Task.CompletedTask;
static async Task Awaited(Controller controller, Task<ActionExecutedContext> task)
{
controller.OnActionExecuted(await task);
} }
} }