2

I have a web API method that gets a dashboard view.

The method calls about 24 different queries. Each query taking about 60ms to execute, I'm using Glimpse to profile.

What I was hoping to do, was run them asynchronously as to avoid the calling of each one, one after the other, thus making it 60ms X 5 method calls.

I'm also new to Async Await, so my expectations may be incorrect.

Here is my Web API Method

[HttpGet]
[ExceptionHandling]
public async Task<DashboardResponse> GetDashboard()
{
    return await DashboardResponse.GetDashboard();
}

Here is the helper method

public static async Task<DashboardResponse> GetDashboard()
{
    var resp = new DashboardResponse();

    resp.MonthGlance = await GetMonthAtAGlance();
    resp.MostRecentOrder = await GetMostRecentOrder();
    resp.CreateAnOrder = await GetCreateAnOrder();
    resp.RecentOrders = await GetRecentOrders();
    resp.RecentNotifications = await GetRecentNotifications();

    var messages = MessageResponse.GetMessages(new MessageFilters() { PageNumber = 1, PageSize = 10 }).Messages;

    resp.RecentMessages.Messages = messages;
    resp.OrderLineChart = GetOrderLineChart();

    return resp;
}

Here is one of the methods called inside the helper method (the rest are setup very similar)

public static async Task<MonthGlanceResp> GetMonthAtAGlance()
{
    var monthAtAGlance = new MonthGlanceResp();

    var monthStart = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
    var monthEnd = monthStart.AddMonths(1).AddDays(-1);

    var monthGlanceQuery = Helpers.DbContext.Current.Orders
                            .Where(c => c.Status != OrderStatuses.Deleted &&
                                (c.OrderSubSource == OrderSubSources.ApplicantLink ||
                                c.OrderSubSource == OrderSubSources.BulkImport ||
                                c.OrderSubSource == OrderSubSources.OrderComprehensive) &&
                                c.DateIn >= monthStart &&
                                c.DateIn <= monthEnd).AsQueryable();

    if (UserContext.Current.AccountAccess == CustomerAccessTypes.Global)
    {
        monthGlanceQuery = monthGlanceQuery.Where(c => c.CustomerId == UserContext.Current.CustomerId);
    }
    else if (UserContext.Current.AccountAccess == CustomerAccessTypes.Account)
    {
        monthGlanceQuery = monthGlanceQuery.Where(c => c.CustomerAccountId == UserContext.Current.CustomerAccountId);
    }
    else
    {
        monthGlanceQuery = monthGlanceQuery.Where(c => c.CustomerAccountPersonId == UserContext.Current.CustomerAccountPersonId);
    }

    monthAtAGlance.ClearOrderCount = await monthGlanceQuery
                                        .CountAsync(c => c.OrderLineItems
                                                        .Where(d => d.ItemType == "package_service" || d.ItemType == "service")
                                                        .All(d => d.Result == OrderLineItemResults.Clear && d.Status == OrderLineItemStatuses.ApprovedForClient));

    monthAtAGlance.QuestionableOrderCount = await monthGlanceQuery
                                        .CountAsync(c => c.OrderLineItems
                                                        .Where(d => d.ItemType == "package_service" || d.ItemType == "service")
                                                        .All(d => d.Status == OrderLineItemStatuses.ApprovedForClient) &&
                                                        c.OrderLineItems
                                                        .Where(d => d.ItemType == "package_service" || d.ItemType == "service")
                                                        .Any(d => d.Result == OrderLineItemResults.Questionable));

    monthAtAGlance.PendingOrderCount = await monthGlanceQuery
                                        .CountAsync(c => c.OrderLineItems
                                                        .Where(d => d.ItemType == "package_service" || d.ItemType == "service")
                                                        .Any(d => d.Status != OrderLineItemStatuses.ApprovedForClient));

    monthAtAGlance.OrderCount = await monthGlanceQuery.CountAsync();

    return monthAtAGlance;
}

The only problem is that after implementing all the async await changes it appears that the web api call is now running slower than before! i'm not sure if I have it structured correctly, or even if what i'm thinking is possible.

svick
  • 236,525
  • 50
  • 385
  • 514
99823
  • 2,407
  • 7
  • 38
  • 60
  • 1
    You are doing your tasks asynchronously, but not in parallel, so you are in effect waiting for each one to complete before executing the next one. This is useful if you need the return value of your async method for the rest the method, if you don't have a look at `Task.WhenAll()` which allows you to wait for a number of parallel tasks http://msdn.microsoft.com/en-gb/library/hh556530.aspx – Ben Robinson Sep 08 '14 at 14:08
  • Async adds some overhead but only a small amount. How much slower is it running? Do you deal in milliseconds? – usr Sep 13 '14 at 18:37

1 Answers1

7

The only problem is that after implementing all the async await changes it appears that the web api call is now running slower than before!

Asynchronous code runs slower than synchronous code, unless you introduce concurrency.

Side note: asynchronous code is often used on server apps (e.g., ASP.NET) even if an individual request does not have concurrency, because even though each individual request is (slightly) slower, the system as a whole can scale further and faster.

So, you need concurrency.

different queries

You might want to think about that a bit. If you're hitting a single instance of SQL server, would you really get any benefit from executing your queries concurrently? Maybe, maybe not. It's best to test this out first.

Note that Entity Framework - while it allows asynchronous queries - only allows one asynchronous query at a time per context. You'll need to create a different context for each concurrent query. So you need to be careful with concurrent queries, since some of your entities are coming from different contexts.

That said, you can do concurrent calls that look like this:

public static async Task<DashboardResponse> GetDashboardAsync()
{
  var resp = new DashboardResponse();
  var monthAtAGlanceTask = GetMonthAtAGlanceAsync();
  var mostRecentOrderTask = GetMostRecentOrderAsync();
  ...
  await Task.WhenAll(monthAtAGlanceTask, mostRecentOrderTask, ...);
  resp.MonthGlance = await monthAtAGlanceTask;
  resp.MostRecentOrder = await mostRecentOrderTask;
  ...
  return resp;
}

Note that your code will almost certainly not be able to use Helpers.DbContext.Current; each concurrent query will require its own context.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Stephen - you bring up some great points, I think I'm really going to have to resetructure how I do things are currently I was trying to run all queries on one context. – 99823 Sep 08 '14 at 15:23