Asynchronous C# method calls in ASP.NET page working on debug, but not working on live site

1.4k views Asked by At

OK Here is the situation:

I have a web app with a table of statistics on our salesmen's customers, and each row has a sparkline graph showing the general trend of the sales data for the last 12 months. Each page shows a particular salesman's customer list, and some of them can have an enormous number of customers = an enormous number of rows = an enormous number of sparklines (e.g. one in particular has 125 and takes 15 seconds to load).

For this reason, jQuery sparklines could not be used - they completely pinned the CPU of a user accessing a page with a lot of sparklines with IE.

So I moved on to using the Google Chart API, which worked much better, except for two issues: 1) it's on a secure site, and the Google Chart API URL is only served over HTTP (solved by using a small wrapper script to download the graph dynamically and re-serve it from our secure server); and 2) on a page with 125 sparklines, it was still very slow due to the number of requests (even when the 0-9 server prefixes are used to maximize the # of available connections).

So my next step beyond this was to try to make each of the "download/grab/re-serve image" method calls asynchronous - and it worked!

...but only on my dev box running in debug mode.

When I pushed it up to the live site, it was faster, but it left some of the images unloaded, which is of course unacceptable.

So here is what I was hoping some SO hotshot would know:

1) Why are my asynchronous method calls working while debugging, but not working on the live site?

2) Is there any easier way to get a large number of sparklines of some sort to load quickly on a secure server without making me want to tear my hair out?

2a.) Does anyone have any experience using the ASP.NET Chart Library? Is this something I should investigate?

2b.) A co-worker suggested I make my own sparkline routine using a 1x1 CSS background image and varying the height. The problems are a) it is completely un-extensible in case we want to make changes; b) it seems hacky as hell (leaves about a bajillion DIVs per sparkline in the markup); and c) I have no idea if it will be fast enough when there are 100-200 of them on one page - what are your thoughts on the feasibility of the 1x1 sprite approach?

Thanks in advance.

2

There are 2 answers

4
Bryn Keller On

ASP.Net Charts work well in my experience. There's a quick starter at http://betterdashboards.wordpress.com/2010/02/21/how-to-create-a-sparkline-chart-in-asp-net/ to get you started with the necessary configuration to render a sparkline.

EDIT: Added sample of using ASP.Net charts in MVC. Obviously you'd want to move some of this code into a helper class of some kind, but hopefully this makes it clear what has to happen to make this work.

    @model IEnumerable<CustomerSales>        
@using Chart = System.Web.UI.DataVisualization.Charting.Chart   
@{    
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<p>
    @Html.ActionLink("Create New", "Create")
</p>
<table>
    <tr>
        <th>
            Name
        </th>
        <th>
            Sales
        </th>
    </tr>

@foreach (var item in Model)
{
    <tr>
        <td>
            @item.Name
        </td>
        <td>
            @{

    var chart = new Chart();
    chart.ChartAreas.Add(new ChartArea());
    chart.ChartAreas[0].AxisX.LabelStyle.Enabled = false;
    chart.ChartAreas[0].AxisY.LabelStyle.Enabled = false;
    chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
    chart.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
    chart.ChartAreas[0].AxisX.MajorTickMark.Enabled = false;
    chart.ChartAreas[0].AxisY.MajorTickMark.Enabled = false;
    chart.ChartAreas[0].AxisX.LineWidth = 0;
    chart.ChartAreas[0].AxisY.LineWidth = 0;

    chart.Series.Add(new Series());
        // Assign the random number to the chart to create 35 points
    for (int x = 0; x < item.Sales.Length; x++)
    {
        chart.Series[0].Points.AddXY(x, item.Sales[x]);
    }

     // Start hiding both sets of axes, labels, gridlines and tick marks
     chart.ChartAreas[0].AxisX.LabelStyle.Enabled = false;
     chart.ChartAreas[0].AxisY.LabelStyle.Enabled = false;
     chart.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
     chart.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
     chart.ChartAreas[0].AxisX.MajorTickMark.Enabled = false;
     chart.ChartAreas[0].AxisY.MajorTickMark.Enabled = false;
     chart.ChartAreas[0].AxisX.LineWidth = 0;
     chart.ChartAreas[0].AxisY.LineWidth = 0;

     // Sparklines use the 'Spline' chart type to show a smoother trend with a line chart
     chart.Series[0].ChartType = SeriesChartType.Spline;

     // Since the line is the only thing you see on the chart, you might want to
     // increase its width.&nbsp; Interestingly, you need to set the BorderWidth property
     // in order to accomplish that.
     chart.Series[0].BorderWidth = 2;

     // Re-adjust the size of the chart to reduce unnecessary white space
     chart.Width = 400;
     chart.Height = 100;
     string base64;
     using (MemoryStream ms = new MemoryStream())
     {
         chart.SaveImage(ms);
         var bytes = ms.GetBuffer();
         base64 = Convert.ToBase64String(bytes);
     }
 /**
  * Here's a simpler example that would do a regular chart, 
  * rather than a sparkline. No way to make the gridlines and axes go
  * away with this helper that I'm aware of.

    var chart = new Chart(
        width: 300,
        height: 100
        )
        .AddSeries(chartType: "Spline", yValues: item.Sales);
    var bytes = chart.GetBytes();
    var base64 = Convert.ToBase64String(bytes);    
             */
                }
             <img src="data:image/jpeg;base64,@base64" />
        </td>
    </tr>
}

</table>

This is using a model that looks like:

public class CustomerSales
{
    public CustomerSales(string name, params int[] sales)
    {
        Name = name;
        Sales = sales;
    }
    [Required]
    public string Name { get; set; }
    [Required]
    public int[] Sales { get; set; }
}
0
Shiv Kumar On

First of all asynchronous doesn't mean faster and in your case it will make no difference if not slow things down even more.

I'm just going to simplify what's going on so I can understand it better. There is a page that loads 125-200 images. These images are dynamically generated graphs. Does that sum up what is going on? If so then:

Loading those many images should not be a problem if the client has sufficient bandwidth and your server can handle the requests and you have a large enough pipe on your server. So where is the delay? Is it Google? Or is your server slowing things down?

how much slower is it as compared to going from browser directly to Google? Are the response times of going from browser directly to Google acceptable? If not maybe you need to use a different strategy in your UI such that all images are not loaded automatically. Or each customer is in a tab or break things up into pages so only small amounts of data and therefore images are requested at a time.

The Http socket you're using will be limited to issuing 2 requests to Google at any time, no matter how mant threads you spawn. There is a way to change this number (but I can't remember off the top of my head how). Anyway, this is most likely the cause of your slowdown.

Using Asynchronous calls (to do some I/O bound operations as in this case) from business layer code is not getting you the benefit of free up the request thread to service other requests. For that to work you'll need to use async pages since the page can inform IIS of this fact. Any other async work simply uses more threads (unless you're using the techniques used in the Async CTP - C# 5.0 and a specifically using I/O threads and not worker threads.

Now given what you're seeing, there are errors on the server side at the time of getting the images from Google and you need to know what those errors are. I would recommended you not use async for this.