Background threads in ASP.net applications (Part 2 – thread implementation)

To continue the saga of developing ASP.net applications that make use of background threads, we will look at how to 'optimize' the application that I have proposed in the first article. The objective would be to have the application load the data in the background via a thread that would update the price of certain stock symbols every X seconds.

To do this, what I have seen many of my customer's do, is to spawn a new .Net thread in the background and have this thread run an infinite loop with the following pseudo-logic:

  1. When the application loads up, start the background thread and have it execute the infinite loop
  2. Once in the infinite loop, the thread will update the price for the symbols in the list
  3. When it has retrieved the information for the price of the symbols, it will sleep for a given amount of time
  4. It will then resume the loop from the beginning and attempt to refresh the prices again.

So let's look how this pseudo logic can be transformed into code. Keep in mind that I am doing the bare minimum to keep the application as simple as possible and focus on the essentials.

I will first start by declaring a POCO (short for Plain Old CRL Object) class that will represent a stock quote. Here is the code listing:

//base libraries

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

//added imports

 namespace NotPatterns
{
  public class StockQuote
{

      //class properties
public string CompanyName { get; set; }

      public string SymbolName { get; set; }

      public double Price { get; set; }

      public int Volume { get; set; }

      public DateTime LastUpdate { get; set; }

      }

}

This class only contains a couple of properties that expose both get and set so that we can store the data related to a stock quote inside an instance of the class. This will represent our Business Layer (Model).

When the application loads into the process memory (into the w3wp.exe worker process), the Application_Start event is fired. This seems like a good place to add the code to create a background thread.

The first method I will implement is located in the Global.asax file (but I could have implemented this inside another class if I had wanted to as well). This method is called UpdateQuotes. Here is the code listing:

  private void UpdateQoutes()
{
//declare an array of strings to contain the stock
string[] stockSymbols = { "MSFT", "AAPL", "GOOG", "AMZN" };

    string url = "https://finance.yahoo.com/webservice/v1/symbols/";

         string stockUrl; StockQuote quote; XDocument xdoc;

    do
{
//go through each of the symbols and run them
foreach (String stockSymbol in stockSymbols)
{
stockUrl = url + Server.UrlEncode(stockSymbol) + "/quote";

          xdoc = XDocument.Load(stockUrl);

          //create a new qoute
quote = new StockQuote();
quote.CompanyName = GetData(xdoc, "name");
quote.Price = Convert.ToDouble(GetData(xdoc, "price"));
quote.Volume = Convert.ToInt32(GetData(xdoc, "volume"));
quote.SymbolName = GetData(xdoc, "symbol");
quote.LastUpdate = DateTime.Now; 

          //save the symbol
Application[stockSymbol] = quote; 

        }

      //sleep for 100 Seconds
System.Threading.Thread.Sleep(100000);

      } while (true);

   }

What the method does is that it will declare an array of four stock symbols (just to keep it simple – we could use something more dynamic afterwards) that will be updated every X seconds. Following this declaration, it will go into an infinite loop, represented by the do{} while(true) statement.

Each of the symbols in the array will be inspected, and we will try to obtain the current price for the symbol, by making a call to the Yahoo Finance API using the XDocument object. For each symbol we compose the correct URL and then we ask the XDocument object to retrieve the XML datagram corresponding to the stock quote.

Once we have loaded the XML inside the XDocument object, the method will create a new instance of the StockQuote class and populate its properties with data extracted from the XML – via the GetData(System.Xml.Linq.XDocument, System.String) method, just like in the previous sample. The newly created instance of StockQuote will be added to the Application object, an object that is available in all parts of the application and that can be thought of as a global dictionary of variables. (Some of you might remark that I could have checked if I already had an entry for the symbol in question inside the Application object and if so, I could just have performed an update on the object instead of creating a new instance every time, and you would be right to do so. However, in essence I am trying to keep the app basic, and not necessarily optimized).

Following the loading of the financial data for all symbols, the loop will put the thread to sleep 100 seconds (1minute 40 seconds). After this, the loop will be executed again and again, until the end of time, or the end of the process, whichever comes first.

Now for the interface to display these four stock symbols in a web-page. To do this, I have created a second page for the application, called AutoStock.aspx. This page contains a repeater control that is strongly–typed data-bound to an object of type StockQuote, via the ItemType property. If you are new to strongly typed data-binding, I would suggest you have a look at my ASP.net 4.5 Webforms video tutorial:

https://linqto.me/n/ASPNet45

Here are the tags for the repeater control – note the ItemType tag that indicates the type of object that will be displayed by the control and the SelectMethod tag which indicates which method in the code behind which will be called to load the elements to display when the control is loaded:

<asp:Repeater ID="rptStocks" runat="server" ItemType="NotPatterns.StockQuote" SelectMethod="GetQuotes">
   <ItemTemplate>
<div style="float:left">
<fieldset>
<legend><%# Item.SymbolName %></legend>
Company Name: <%# Item.CompanyName %>
<br />
<br />
Price: <%# Item.Price %> $
<br />
<br />
Volume: <%# Item.Volume %> shares
<br />
<br />
Last Updated: <%# Item.LastUpdate.ToLongTimeString() %>
</fieldset>
</div>
</ItemTemplate>
</asp:Repeater>

Inside the repeater control, we just display a fieldset tag for each stock quote, and inside this we display, the company name, the price and other information via strongly typed data-binding.

In the code behind, the GetQuotes() method does all the loading of the pricing data from the Application object – here is the code:

public IEnumerable<NotPatterns.StockQuote> GetQuotes()
{
//get the stocks from the application object
List<NotPatterns.StockQuote> stocks = new List<NotPatterns.StockQuote>();

    //load the stocks
if (Application.Count == 4)
{
stocks.Add((NotPatterns.StockQuote)Application["MSFT"]);
stocks.Add((NotPatterns.StockQuote)Application["AAPL"]);
stocks.Add((NotPatterns.StockQuote)Application["GOOG"]);
stocks.Add((NotPatterns.StockQuote)Application["AMZN"]);

    return stocks;

}

The method declares a variable of type List<StockQuote> and attempts to add the stock quotes from the Application object to the newly created list. We check if the count of items in the Application object is equal to 4, since we only have four items in the stock array defined in the Global.asax. Should the background thread not have finished loading the prices for the first time before we show the page, we don't want to show null data and crash the page, hence the reason for the test. It ensures that the data was loaded by the background thread at least once.

There is also a refresh button on the page, witch a Click event handler in the code behind:

protected void cmdRefresh_Click(object sender, EventArgs e)
{
    //force the rebinding
    rptStocks.DataBind();
}

All this does is that it forces the repeater control to data-bind once again – and hence to call the GetQuotes() method and reload the data from the Application object. Note that the data will not be reloaded by the background thread when this new call to GetQuotes() comes in. It will be refreshed only when the background thread wakes up the next time around and loads new data from Yahoo.

You can find the sample completed with this new code in my OneDrive as well, by following this link:

https://onedrive.live.com/?cid=9A83CB6EACC8118C&id=9A83CB6EACC8118C%21131

In the next installment, I will discuss the problems with this kind of pattern and why it is not recommended you make use of it in your ASP.net applications.

By Paul Cociuba
https://linqto.me/about/pcociuba

Comments