I have build many small applications so far in MVC to help myself in understanding the framework more deeply. Now I am planning to write a blog engine on MVC. It will not be an easy task but I will be going to learn a lot of things. As I was planning things ahead I came across a problem where I have to load my blog stats like FeedBurner readers, Facebook page likes, Google +1 count, twitter followers inside a div on the page. The problem I see here is that I am going to make multiple cross-domain calls in order to get the stats. I can’t avoid that though but this will cause my blog to load slowly. I know about Async and Task Parallel Library(TPL) but the question is how do I implement this in my application? I do a quick web search and found something which is called Async controller in MVC4. You can also have AsynController in MVC3 but that is not pretty if you start comparing it with MVC4.
For the simplicity of the tutorial I am going to make 2 calls: get twitter followers count and facebook page likes. In MVC3, if you want to perform Async operations then you have to inherit your controller with AsyncController like this.
public class HomeController : AsyncController
But MVC4 is much cleaner. Let’s take a look at the default implementation of the calls I am making to Twitter and Facebook to get the follower count and page likes respectively. Here are my 2 methods in HomeController.
private string GetTwitterFollowersCount(string Username) { XDocument xdoc = XDocument.Load("http://api.twitter.com/1/users/show.xml?screen_name=" + Username + "&include_entities=false"); return (from item in xdoc.Descendants("user") select item.Element("followers_count").Value) .SingleOrDefault(); } private string GetFacebookLikes(string FaceBookPageURL) { string uri = "https://api.facebook.com/method/fql.query?query=select%20%20like_count,%20total_count,%20share_count,%20click_count%20from%20link_stat%20where%20url=%22" + FaceBookPageURL + "%22"; return Convert.ToString((from elem in XElement.Load(uri).Descendants() where elem.Name.LocalName == "like_count" select elem).First<XElement>().Value); }
Nothing fancy in the above two methods. When I call these 2 methods one after the other, they will just get the data from a different source and render the result in the view. That’s fine as it’s my requirement. Now when I run my application it now takes a longer time to render the view. Before I explain this further take a look at my Index()
.
public ActionResult Index() { Stopwatch watch = new Stopwatch(); watch.Start(); ViewBag.TwitterFollowers = GetTwitterFollowersCount("audi"); ViewBag.FacebookLikes = GetFacebookLikes("http://facebook.com/audi"); watch.Stop(); Int64 elapsedTime = watch.ElapsedMilliseconds; ViewBag.Time = elapsedTime.ToString(); return View(); }
This is because Controllers are synchronous by default and it will process the request one after the other i.e. it will wait for the first task to complete and then move on to the next task. In my case the controller will execute the GetFollowersCount
method and only after the completion of this method it will shift to the next method GetFacebookLikes
. This could be a long running task or just imagine if you are retrieving heavy data from different sources. The view or the page will not render till the execution of both the methods will not get completed. To get a rough idea of the time elapsed in making synchronous calls in the above controller, I have used StopWatch(System.Diagnostics) class. I don’t know how accurate, but I still used it to have an approximate idea of the time. This is the output of the above code.
It took 1588ms
to complete 2 requests. Now if I implement async in the controller I will see a drastic improvement. It is not just the controller ActionResult
which I have to change to support async calls but I also have to update the 2 methods which I am using to collect the stats. Here are my updated methods.
private Task<string> GetTwitterFollowersCountAsync(string Username) { return Task.Run(() => { XDocument xdoc = XDocument.Load("http://api.twitter.com/1/users/show.xml?screen_name=" + Username + "&include_entities=false"); return (from item in xdoc.Descendants("user") select item.Element("followers_count").Value).SingleOrDefault(); }); } private Task<string> GetFacebookLikesAsync(string FaceBookPageURL) { return Task.Run(() => { string uri = "https://api.facebook.com/method/fql.query?query=select%20%20like_count,%20total_count,%20share_count,%20click_count%20from%20link_stat%20where%20url=%22" + FaceBookPageURL + "%22"; return Convert.ToString((from elem in XElement.Load(uri).Descendants() where elem.Name.LocalName == "like_count" select elem).First<XElement>().Value); }); }
Pay attention to the GetTwitterFollwersCountAsync
and GetFacebookLikesAsync
return type. Previously they were only string, and now I have changed their return type to Task. I have put the code inside Task.Run() as it will queues the specified work to run on the Thread Pool and returns a Task(TResult)
handle for that work. I have changed the ActionResult to handle the async feature. As you can see that this controller method does not return ActionResult instead it return Task. Here is my updated ActionResult.
public async Task<ActionResult> Index() { Stopwatch watch = new Stopwatch(); watch.Start(); Task<string> twitterFollowers = GetTwitterFollowersCountAsync("audi"); Task<string> facebookLikes = GetFacebookLikesAsync("http://facebook.com/audi"); await Task.WhenAll(twitterFollowers, facebookLikes); ViewBag.TwitterFollowers = await twitterFollowers; ViewBag.FacebookLikes = await facebookLikes; watch.Stop(); Int64 elapsedTime = watch.ElapsedMilliseconds; ViewBag.Time = elapsedTime.ToString(); return View(); }
The code is almost the same but 3 lines are making a difference (highlighted). Task will hold the value returned from the updated async methods. MSDN defines the await operator as:
The await operator is applied to a task in an asynchronous method to suspend the execution of the method until the awaited task completes. The task represents ongoing work.
Task.WhenAll() will creates a task that will complete when all of the supplied tasks have completed. This actually parallelize the request. Lets check the output and compare both.
The difference is noticeable…607ms!! is half way down and a huge performance benefit. This is a small example and the real performance can be noticed when you work with real scenarios. I have used MVC4 with .NET 4.5 to use awesomeness of asynchronous controller. I hope this gives you an idea on how you can use async controllers in MVC4.