Question

I am not so good at asynchronous programming so the question might be at the low level.

I have created an async method as below with Async CTP on ASP.NET MVC 4 Dev. Preview:

public class Movie
{
    public string Title { get; set; }
    public string Url { get; set; }
    public string BoxArtUrl { get; set; }
}

public class MovieM {

    public IEnumerable<Movie> M2009 { get; set; }
    public IEnumerable<Movie> M2010 { get; set; }
    public IEnumerable<Movie> M2011 { get; set; }
}

public class HomeController : AsyncController  {

    public async Task<ActionResult> GetMoviesM()  {

        var profiler = MiniProfiler.Current; // it's ok if this is null

        var pageSize = 1000;
        var imageCount = 0;

        using (profiler.Step("Start pulling data (Async) and return it")) { 

            var m2009 = await QueryMoviesAsync(2009, imageCount, pageSize);
            var m2010 = await QueryMoviesAsync(2010, imageCount, pageSize);
            var m2011 = await QueryMoviesAsync(2011, imageCount, pageSize);

            return View(new MovieM { 
                M2009 = m2009,
                M2010 = m2010,
                M2011 = m2011
            });
        }
    }

    XNamespace xa = "http://www.w3.org/2005/Atom";
    XNamespace xd = "http://schemas.microsoft.com/ado/2007/08/dataservices";
    XNamespace xm = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

    string query = "http://odata.netflix.com/Catalog/Titles?$filter=ReleaseYear eq {0}&$skip={1}&$top={2}&$select=Url,BoxArt";

    async Task<IEnumerable<Movie>> QueryMoviesAsync(int year, int first, int count)  {

        var client = new WebClient();
        var url = String.Format(query, year, first, count);
        var data = await client.DownloadStringTaskAsync(new Uri(url));
        var movies =
            from entry in XDocument.Parse(data).Descendants(xa + "entry")
            let properties = entry.Element(xm + "properties")
            select new Movie
            {
                Title = (string)entry.Element(xa + "title"),
                Url = (string)properties.Element(xd + "Url"),
                BoxArtUrl = (string)properties.Element(xd + "BoxArt").Element(xd + "LargeUrl")
            };
        return movies.AsEnumerable();
    }
}

The code works just fine. When we run the same function on a desktop app (a WPF App for instance), we can see a tangible performance difference. The UI isn't blocked, the data is being pushed to screen instantly when it is available.

But on a web application, I really cannot see a difference. I also created the same function as sync and the both of them are nearly the same.

What I would like to know is that:

  1. I run this app on a machine which has Intel Core 2 Duo CPU T5750 2.00GHz. Do number of processors effect the performance of asynchronous thread on C#?
  2. Am I doing something wrong here from a web application point of view?
Was it helpful?

Solution

When we run the same function on a desktop app (a WPF App for instance), we can see a tangible performance difference. The UI isn't blocked, the data is being pushed to screen instantly when it is available.

This is a common misconception. The method you posted is slightly slower than its synchronous equivalent, but in a UI app it is more responsive, and therefore appears more performant.

In an ASP.NET scenario, the page is only rendered when all asynchronous requests have completed; that is why you don't see a difference.

You can make it actually more performant by parallelizing your requests:

var m2009Task = QueryMoviesAsync(2009, imageCount, pageSize);
var m2010Task = QueryMoviesAsync(2010, imageCount, pageSize);
var m2011Task = QueryMoviesAsync(2011, imageCount, pageSize);
await Task.WhenAll(m2009Task, m2010Task, m2011Task);
var m2009 = await m2009Task;
var m2010 = await m2010Task;
var m2011 = await m2011Task;

This will improve the performance for both desktop and ASP.NET.

Your original code uses serial composition (one await at a time). In this case, the code is running asynchronously, but the request is not completed any faster. There is still a benefit to using async/await like this: the request does not tie up an ASP.NET thread, so your service can scale up more. In the UI world, the UI thread does not get tied up, so it's more responsive. But for both ASP.NET and UI, the total time to complete the GetMoviesM method is not reduced by making it async.

Parallel composition (using Task.WhenAll or Task.WhenAny) allows GetMoviesM as a whole to run faster, since it does the requests in parallel. In addition, you get the thread benefits mentioned above.

In this case, the number of processors does not matter. They only come into play when you're doing processing on the thread pool (e.g., Task.Run), not when doing I/O. (This is a simplification, but true enough).

OTHER TIPS

A few comments.

Firstly, WebClient by default only opens 2 connections per server, per session. This will obviously impact your scaling ability, so you might want to change that [see How can I programmatically remove the 2 connection limit in WebClient ]

Secondly, I'm not sure there's any benefit to using async both in your controller method AND inside the QueryMoviesAsync method.

Thirdly, WebClient implements IDisposable, so you should be using it with a using(..) statement. Not doing so may affect scalability as well.

Given all the above changes, and to answer your original question, yes the code should scale out over multiple processors/cores at runtime as that is the default for ASP.NET/IIS

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top