How much async is too much?

How much async is too much?

Old forum URL: forums.lhotka.net/forums/t/12349.aspx


Brad Rem posted on Friday, February 07, 2014

For an ASP.NET MVC application I'm wondering how much is too much. Here's some example code that I've tried to make as async as possible.

For MVC, at what point is async not doing us any favors?

Drilling down from the Controller action:

public async Task<ActionResult> DetailsAsync(int id)
{
  ViewData.Model = await ProjectEdit.GetProjectAsync(id);
  return View();
}

CSLA business object and DAL:

public static async Task<ProjectEdit> GetProjectAsync(int id)
{
  return await DataPortal.FetchAsync<ProjectEdit>(id);
}

private async Task DataPortal_FetchAsync(int id)
{
  using (var ctx = ProjectTracker.Dal.DalFactory.GetManager())
  {
    var dal = ctx.GetProvider<ProjectTracker.Dal.IProjectDal>();
    var data = await dal.FetchAsync(id);
    using (BypassPropertyChecks)
    {
        id = data.Id;
        //....
    }
  }
}

public async Task<ProjectDto> FetchAsync(int id)
{
  using (var ctx = ObjectContextManager<PTrackerEntities>.GetManager("PTrackerEntities"))
  {
    var result = from r in ctx.ObjectContext.Projects
                 where r.ProjectId == id
                 select new ProjectDto
                 {
                   ProjectId = r.ProjectId
                 };
    return await result.FirstAsync();
  }
}

RockfordLhotka replied on Tuesday, February 25, 2014

Remember that async usually does NOT mean multiple threads. It means concurrent IO.

What you get from concurrent IO on a web server is really two things.

First, the web server gets to reuse your thread when you code gets blocked by IO - at least that's my understanding. This improves the overall scalability of the server, but does nothing for your particular web request.

Second, IF you are doing multiple concurrent IO requests, and IF you structure your code to make those requests concurrently, then your web request might gain some performance because the IO requests are concurrent instead of sequential.

If you do this:

var a = await obi.Request1();
var b = await obi.Request2();

Then you get nothing, because these requests will be "async", but sequential. So what you need to do is something like this (from memory, so might not be perfect):

var tasks = new List<Task>();
tasks.Add(obj.Request1());
tasks.Add(obj.Request2());
await Tasks.WaitAll(tasks);

It is something like that anyway - basically get the Task objects from the methods you want to run concurrently, then use a wait statement so you can await the completion of all the tasks in the list. 

Brad Rem replied on Monday, March 10, 2014

What my question really asks is how deep do we take the async and can we end up penalizing ourselves?

I understand (or think I do) how web requests work.  I envision it like a sort-of-fast food restaurant:

Customers arrive at the counter and ask a Worker for food (let's just call the stuff fast-food produces "food"). The Worker brings the Customer food.

So, a Customer arrives and asks for Fries. The Worker goes over the Fry station and drops a basket of Fries into the boiling oil.  In a synchronous scenario, the Worker would remain at the Fry station until the Fries were done. What's bad about this is that you have Customers queuing up at the counter with nobody to serve them.

But, in the asynchronous model, the Worker would drop the Fries into the oil, set the timer alarm, and return back to the counter to take another order. While the Fries are cooking, they could take an order for a drink or an ice cream or something like that, completing the order for several customers. When the Fries are done and the alarm goes off, a free Worker goes gets the Fries and gives them to the customer that originally asked for them.

If we modeled that in MVC with CSLA:

 
// the controller action
public async Task<ActionResult> GetFries()
{
   var fries = await Fries.GetCollectionAsync();

    return View(new FryModel(fries));
}

// the code in the CSLA business class
public static async Task<Fries> GetCollectionAsync()
{
  return await DataPortal.FetchAsync<Fries>();
}

If a Customer makes a web request to GetFries(), thanks to our await, we free up the request thread so it can handle other requests while the IO of the CSLA object is happening. 

And now we're just getting to the kernel of my question.  In this mythical Fries CSLA collection, what would the difference of these two DataPortal_Fetch methods be if you consider the above code?

private void DataPortal_Fetch()
{
  using (var ctx = FastFood.Dal.DalFactory.GetManager())
  {
    var dal = ctx.GetProvider<FastFood.Dal.IFryDal>();
    var rows = dal.Fetch();
    foreach (var row in rows)
    {
        Add(Fry.GetChild(row));
    }
  }
}

---- or ------

private async Task DataPortal_FetchAsync()
{
  using (var ctx = FastFood.Dal.DalFactory.GetManager())
  {
    var dal = ctx.GetProvider<FastFood.Dal.IFryDal>();
    var rows = await dal.FetchAsync();
    foreach (var row in rows)
    {
        Add(Fry.GetChild(row));
    }
  }
}

The first DataPortal_Fetch does not contain any asynchronous calls, but the second DataPortal_Async awaits the call to the DAL where you can presume that there are some async calls with Entity Framework fetching from a SQL server.

I should ask, though, Is it true that I could interchangability use either of the two above DataPortal Fetch methods with this calling code:
   
    // this will either work with DataPortal_Fetch or DataPortal_FetchAsync, right?
    await DataPortal.FetchAsync<FryList>();

What I'm driving at is that in regards to ASP.NET, I don't see any real difference between making the DAL asynchronous.

Said another way, AFTER you call "await DataPortal.FetchAsync<FryList>()", does it even matter if the DataPortal_Fetch or the calls to the ORM are synchronous or asynchronous? 

I'm tending to believe that after you call "await DataPortal.FetchAsync" it no longer matters if the methods deeper than that call contain asynchronous calls.

RockfordLhotka replied on Monday, March 10, 2014

It is _very_ important to understand that async doesn't mean parallel. It might, but it might not, and usually it doesn't.

Using the async/await keywords usually does _not_ result in a background thread running a background task. Usually what happens is that it results in a blocking IO operation and your original (and only) thread is able to do some other work until the IO operation completes. In ASP.NET that thread may not even be working for you, it might be servicing some other request.

So on a web server using async helps the scalability of the server as a whole, not necessarily your app.

On a smart client the same thing is generally true - usually blocking IO - but the benefit there is that the UI remains responsive to the user - that's what the would-be-idle thread is doing is processing the Windows message queue.

Now if you actually create a thread (Task.Run or something) then async really is parallel in that you have multiple CPU activities running at once. That's not what normally happens though, because most of the libraries you call are doing IO at some level, not spinning up new threads.

Copyright (c) Marimer LLC