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
{
@ -292,7 +292,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
var isCompleted = false;
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)
@ -304,14 +308,59 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
}
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);
if (_actionExecutingContext.Result != null)
{
// 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(
typeof(IAsyncActionFilter).Name,
nameof(ActionExecutingContext.Result),
@ -320,69 +369,119 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure
throw new InvalidOperationException(message);
}
await InvokeNextActionFilterAsync();
Debug.Assert(_actionExecutedContext != null);
return _actionExecutedContext;
#pragma warning restore CS1998
}
private async Task InvokeActionMethodAsync()
private Task InvokeActionMethodAsync()
{
var controllerContext = _controllerContext;
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
if (_diagnosticListener.IsEnabled() || _logger.IsEnabled(LogLevel.Trace))
{
diagnosticListener.BeforeActionMethod(
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());
return Logged(this);
}
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(
controllerContext,
arguments,
controllerContext,
result);
_result = actionResultValueTask.Result;
}
else
{
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
/// variables in this method represent.</remarks>
protected override async Task InvokeInnerFilterAsync()
protected override Task InvokeInnerFilterAsync()
{
var next = State.ActionBegin;
var scope = Scope.Invoker;
var state = (object)null;
var isCompleted = false;
while (!isCompleted)
try
{
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]
public virtual void OnActionExecuting(Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { }
[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; }
[Microsoft.AspNetCore.Mvc.NonActionAttribute]
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>
/// <returns>A <see cref="Task"/> instance.</returns>
[NonAction]
public virtual async Task OnActionExecutionAsync(
public virtual Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
@ -357,7 +357,20 @@ namespace Microsoft.AspNetCore.Mvc
OnActionExecuting(context);
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);
}
}