3

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?
John Saunders
  • 160,644
  • 26
  • 247
  • 397
tugberk
  • 57,477
  • 67
  • 243
  • 335
  • 1
    It starts to get interesting as you ad more parallel requests: obviously, if there is less contention, each can work better and faster (less context switches, less waiting for pooled threads to be available before starting) – Marc Gravell Dec 05 '11 at 18:31
  • @MarcGravell Thanks for the response Mark. So, you are saying that this will give a better performance if there is more requests on the app. Am I on the right track here? – tugberk Dec 05 '11 at 18:35
  • @MarcGravell also, do you see any bad parts on the code? – tugberk Dec 05 '11 at 18:36
  • Didnt read it (I'm on mobile), but : yes; parallelisation is hugely impacted by concurrent requests – Marc Gravell Dec 05 '11 at 18:40

2 Answers2

3

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

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • This is what I was looking for. thanks! In the example above, I benefit the new VS AsyncCTP. On your sample code, you also benefited that. For you exmaple, what would be the code if we need to do it old-fashioned way? – tugberk Dec 07 '11 at 08:34
  • You could have `QueryMovies` decrement (using interlocked) a shared count initialized to 3, and run the rest of the method when the count reaches 0. – Stephen Cleary Dec 07 '11 at 15:49
0

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

Community
  • 1
  • 1
geoffreys
  • 1,107
  • 7
  • 24