Code Outside of the Box

« Improving Async Support in ASP.NET MVC - Part 1 Improving Async Support in ASP.NET MVC - Part 3 - Async Filter API »

Improving Async Support in ASP.NET MVC - Part 2 - Refactoring AsyncControllerActionInvoker

First published on March 13, 2017

Last time I began by discussing several of the shortcomings of the current ASP.NET MVC async implementation. Specifically the lack of async filters (action filters, etc.).

I’ve decided that the most straight-forward approach is implement a refactored AsyncControllerActionInvoker. This is the component within the MVC pipeline responsible for invoking filters.

I’ve started a new project, MvcAsync, for the refactored code.

Refactoring APM to TAP

As I mentioned, most of the MVC pipeline is written using the classic Asynchronous Programming Model (APM) which requires Begin/End method pairs. This results in very convoluted code that is difficult to reason about.

Fortunately it is possible to wrap APM using the Task-based Asynchronous Pattern (TAP). Stephen Cleary has written and great deal about the topic and has some wonderful resources.

You can compare my implementation to the original to see just how stark the difference is.

And because I wrapped the incoming and outgoing APM, I was largely able to re-used the existing unit tests to assert that my implementation is correct (for now).

Performance

By wrapping both in the incoming and outgoing APM methods in Tasks, I was originally worried about performance. So of course I decided to create a benchmark (using the fantastic BenchmarkDotNet library).

I was pleasantly surprised by the results. Performance is nearly the same, and sometimes better in my Task-based implementation.

                                                        Method |       Mean |    StdDev |
-------------------------------------------------------------- |----------- |---------- |
 AsyncControllerActionInvokerEx_BeginInvokeAction_NormalAction | 17.5357 us | 0.5481 us |
      AsyncControllerActionInvokerEx_InvokeAction_NormalAction | 16.9045 us | 0.2451 us |
   AsyncControllerActionInvoker_BeginInvokeAction_NormalAction | 17.1643 us | 0.1946 us |

Of course I’m assuming there would be an even greater improvement in performance if the entire pipeline could be refactored to use TAP. Unfortunately that would require a lot of changes, many of them breaking changes.

Using AsyncControllerActionInvokerEx at Runtime

Fortunately it’s pretty easy to swap in a custom ActionInvoker at runtime. As a developer you have a few options:

Next Time

Next time I hope to start planning out the API for new async filters.

Comments

Comments are not moderated. But I reserve the right to delete anything hostile, offensive, or SPAMy.