Async Actions in Monorail
One of the ways to significantly increase the scalability of our applications is to start performing more and more things in an asynchronous manner. This is important because most actions that we perform in a typical application are not CPU bound, they are I/O bound.
Here is a simple example:
public class HomeController : SmartDispatcherController { IUserRepository userRepository; public void Index() { PropertyBag["userInfo"] = userRepository.GetUserInformation(CurrentUser); } }
GetUserInformation() is a web service call, and it can take a long time to run. Now, if we have the code as above, while the web service call is being processed, we can't use this thread for any other request. This has a significant implications on our scalability.
ASP.Net has support for just those scenarios, using async requests. You can read more about it here, including a better explanation of the
I recently added support for this to MonoRail (it is currently on a branch, and will be moved to the trunk shortly).
Let us say that you have found that the HomeController.Index() method is a problem from scalability perspective. You can now rewrite it like this:
public class HomeController : SmartDispatcherController { IUserRepository userRepository; public IAsyncResult BeginIndex() { return userRepository.BeginGetUserInformation(CurrentUser, ControllerContext.Async.Callback, ControllerContext.Async.State); } public void EndIndex() { PropertyBag["userInfo"] = userRepository.EndGetUserInformation(ControllerContext.Async.Result); } }
That is all the change that you need to do to turn an action async. We are using the standard .NET async semantics, and MonoRail will infer them automatically and use the underlying ASP.Net async request infrastructure.
"BeginIndex/EndIndex" are the method pairs that compose the async method index. Note that the only thing that needs to be change is turning a synchronous action to an asynchronous action is splitting the action method into an IAsyncResult Begin[ActionName]() and void End[ActionName](). Everything else is exactly the same.
This is a major improvement for scaling an application. The more I learn about async programming, the more it makes sense to me, and I think that I am moving more and more to this paradigm.
Comments
This is a very promising new feature. Its things like this that I hope will continue to differentiate MonoRail from MS MVC.
awesome to the max
Why the need for 2 calls to ControllerContext.Async.Callback in BeginGetUserInformation?
return userRepository.BeginGetUserInformation(CurrentUser,
Just a bug or something reasonable behind the consept?
OMG this is awesome!
Opps, that is a mistake, it is supposed to be:
return userRepository.BeginGetUserInf
ormation(CurrentUser,
A typo when writing the post. I fixed it, thanks for noticing
I'm maybe difficult today, but why do u not send in only the ControllerContext.Async to the repository on both calls and let it decide what it need from the Async object?
public IAsyncResult BeginIndex()
The standard async invocation pattern on the CLR calls for the following method signatures:
BeginXyz(AsyncCallback callback, object state);
EndXyz(IAsyncResult ar);
You will see that in all the API that handles async actions
And what if I need to make two or more WS calls?
CompositeAsyncResult :-)
Wow, that's impressing, can't wait to get it from the trunk. By the way is it possible to handle async timeouts like in the RegisterAsyncTask method?
I suggest this less verbos syntax:
public AsyncEndAction Index()
{
IAsyncResult result = userRepository.BeginGetUserInformation(CurrentUser,
return delegate
{
}
}
dimitrod,
The way this is handled in RegisterAsyncTask is complex, and highly dependent on the WebForms infrastructure.
Basically, they setup a time for this to catch this.
Yes, we can do that, but it is not trivial.
Awesome!
How can you chain several blocking operations and handlers between Begin and End? e.g.
process -> (db query) -> process -> (web service) -> process -> response
Anything similar to "deferred" from Twisted? (http://twistedmatrix.com/projects/core/documentation/howto/defer.html)
Just saw the reference to CompositeAsyncResult, I'll check it out :)
Comment preview