Async Controller Actions in Sitecore
So let's start from the quick intro about async programming in general and how it works.
One of the biggest problems of software is the operations which require long time processing. User runs such an operation and it blocks the main thread of execution until this operation is completed. As a result, we've got non user-friendly software and unhappy users. I bet you know this kind of software I'm talking about ;)
In case of asynchronous programming you don't have to block/hold execution flow, you can simply process something else while your operation executes and comes back again when the result is ready. While the long running process busy doing it’s “evil” job your application can do some useful stuff.
The correct way of using async operations will give you better performance in ASP.NET applications with virtually no extra efforts.
After I've read this article by Kam Figy I was really curious of the subject, because in a perfect world async operations could provide an impossible ;) – they will make Sitecore pages load in a blink of an eye by using power of CPU cores and magic of parallel execution.
Unfortunately, Sitecore doesn't support async execution out of the box and accordingly to the above article you will have to do some overrides in Sitecore pipelines to use AsyncControllerFactor and it completely does make sense.
namespace Web.Pipelines.Initialize { public class InitializeAsyncControllerFactory : InitializeControllerFactory { protected override void SetControllerFactory(PipelineArgs args) { SitecoreControllerFactory controllerFactory = new SitecoreAsyncControllerFactory(ControllerBuilder.Current.GetControllerFactory()); ControllerBuilder.Current.SetControllerFactory(controllerFactory); } } public class SitecoreAsyncControllerFactory : SitecoreControllerFactory { public SitecoreAsyncControllerFactory(IControllerFactory innerFactory) : base(innerFactory) { } protected override void PrepareController(IController controller, string controllerName) { if (!MvcSettings.DetailedErrorOnMissingAction) { return; } System.Web.Mvc.Controller controller2 = controller as System.Web.Mvc.Controller; if (controller2 == null) { return; } /* BEGIN PATCH FOR ASYNC INVOCATION (the rest of this method is stock) */ IAsyncActionInvoker asyncInvoker = controller2.ActionInvoker as IAsyncActionInvoker; if (asyncInvoker != null) { controller2.ActionInvoker = new Sitecore.SitecoreAsyncActionInvoker(asyncInvoker, controllerName); return; } /* END PATCH FOR ASYNC INVOCATION */ IActionInvoker actionInvoker = controller2.ActionInvoker; if (actionInvoker == null) { return; } controller2.ActionInvoker = new SitecoreActionInvoker(actionInvoker, controllerName); } } public class SitecoreAsyncActionInvoker : SitecoreActionInvoker, IAsyncActionInvoker { private readonly IAsyncActionInvoker _innerInvoker; public SitecoreAsyncActionInvoker(IAsyncActionInvoker innerInvoker, string controllerName) : base(innerInvoker, controllerName) { _innerInvoker = innerInvoker; } public IAsyncResult BeginInvokeAction(ControllerContext controllerContext, string actionName, AsyncCallback callback, object state) { return _innerInvoker.BeginInvokeAction(controllerContext, actionName, callback, state); } public bool EndInvokeAction(IAsyncResult asyncResult) { return _innerInvoker.EndInvokeAction(asyncResult); } } }
And we need to add this to the sitecore pipeline:
<initialize> <processor type="Web.Pipelines.Initialize.InitializeAsyncControllerFactory, Web" patch:instead="*[@type='Sitecore.Mvc.Pipelines.Loader.InitializeControllerFactory, Sitecore.Mvc']"/> </initialize>
But if you will try to make controller renderings load asynchronously, you'll definitely get yellow screen with:
"The asynchronous action method '{YOUR ACTION NAME}' returns a Task, which cannot be executed synchronously."
As you can see, such implementation works only if you request your controller action directly for instance, from browser.
Let's try to understand what's happening when you want to produce Sitecore controller rendering. Sitecore will run pipeline which has two processors to handle MVC requests. After a couple of hours of investigation and decompiling Sitecore.Mvc.dll I've found out that Sitecore implementation is very rigid and efforts to make Sitecore work asynchronously proves to be too much of a hard work, because you'll have to override almost entire Sitecore.Mvc assembly.
In a nutshell, async operations in Sitecore are still a sweet dream for developers and this is not related to MVC only, but to executing pipelines, search and other general Sitecore APIs. Hopefully, Sitecore will take care of it in future releases. Fingers crossed ;)