Ayende @ Rahien

Refunds available at head office

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

shawn
03/25/2008 10:42 PM by
shawn

This is a very promising new feature. Its things like this that I hope will continue to differentiate MonoRail from MS MVC.

Chadly
03/25/2008 11:30 PM by
Chadly

awesome to the max

Benny Thomas
03/26/2008 08:22 AM by
Benny Thomas

Why the need for 2 calls to ControllerContext.Async.Callback in BeginGetUserInformation?

return userRepository.BeginGetUserInformation(CurrentUser,

        ControllerContext.Async.Callback,

        ControllerContext.Async.Callback);

Just a bug or something reasonable behind the consept?

Diego Guidi
03/26/2008 08:40 AM by
Diego Guidi

OMG this is awesome!

Ayende Rahien
03/26/2008 08:56 AM by
Ayende Rahien

Opps, that is a mistake, it is supposed to be:

return userRepository.BeginGetUserInf

ormation(CurrentUser,

                   ControllerContext.Async.Callback,

                   ControllerContext.Async.State);

A typo when writing the post. I fixed it, thanks for noticing

Benny Thomas
03/26/2008 09:08 AM by
Benny Thomas

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()

{

    return userRepository.BeginGetUserInformation(CurrentUser, 

        ControllerContext.Async);

}


public void EndIndex()

{

    PropertyBag["userInfo"] = userRepository.EndGetUserInformation(ControllerContext.Async);

}
Ayende Rahien
03/26/2008 09:13 AM by
Ayende Rahien

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

Yuriy
03/26/2008 12:07 PM by
Yuriy

And what if I need to make two or more WS calls?

dimitrod
03/26/2008 03:33 PM by
dimitrod

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?

lanwin
03/29/2008 07:27 PM by
lanwin

I suggest this less verbos syntax:

public AsyncEndAction Index()

{

IAsyncResult result = userRepository.BeginGetUserInformation(CurrentUser,

        ControllerContext.Async.Callback,

        ControllerContext.Async.State);

return delegate

{

PropertyBag["userInfo"] = userRepository.EndGetUserInformation(result);

}

}

Ayende Rahien
03/30/2008 11:25 AM by
Ayende Rahien

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.

Ori Peleg
04/01/2008 08:44 AM by
Ori Peleg

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)

Ori Peleg
04/01/2008 08:54 AM by
Ori Peleg

Just saw the reference to CompositeAsyncResult, I'll check it out :)

Comments have been closed on this topic.