eople hate to wait. In fact, when it comes to a computer, they hate to wait more than about 200 milliseconds. This can be a problem when your Web-based application uses a process that takes many seconds or even minutes. You can’t just put up a progress dialog or a wait cursor.
Fortunately ASP.NET offers a few different solutions for handling such long-running processes, which vary depending on the level of interactivity required and the amount of complexity you are willing to handle. This article will start with a sample application to demonstrate the problem and run through two solutions: one using a simple polling technique, and the other a more advanced AJAX solution.
Be warned that there are some false steps to be found in the .NET framework. One in particular is the IAsyncHTTPHandler, which at first glance, looks like it will help with long web page requests. However, this asynchronous HTTP handler is designed to free up the processor when some task on a page takes some time but doesn’t require any CPU. A good example is making a Web request in the middle of a page. In this case the asynchronous HTTP handler is very efficient.
For this article, I’ll be focusing on a different problem. In the sample application I have created a page that reports the current temperature, wind, and other weather elements for five different airports. It takes the web service about five seconds to get each data. So if I let the page run as shown in Figure 1 it takes almost a minute before the server returns a page?an unacceptable wait time for any user.
The HTML for the long-running page is shown in Listing 1. The page load event code creates a data set for the grid. Then it runs through the various airports and calls the web service to get the data. The method then stuffs the data from the web service into the data set and attaches it to the grid control (see Figure 2).
The WSDL for this web service is http://www.capeclear.com/AirportWeather.wsdl. It defines a bunch of different methods, only one of which I’ll use: The getSummary method, which returns a block of data including the location of the airport, the wind, the sky conditions, the visibility, and more.
Moving to Threads
A threading solution provides a much cleaner experience to the user because they get updates of the processing on a periodic basis. Those responses are easy to prepare so they come back instantly even though the processing in the background might take a while.
To handle the threading system I’ll use two classes and one interface. The JobHandler singleton maintains a set of objects that implement the IJob interface. The JobHandler manages the threads for the system. When a job is added a new thread is created and the Start method is called on the job within a new thread. An ID string is returned that can then be used to lookup the job later.
The UML for the system is shown in Figure 3.
|Figure 3. Picture This: The screen shot shows the UML for the Job system.|
The WeatherJob is an implementation of IJob that polls the weather from a specified set of airports and fills up a DataSet named Data that contains the weather reports.
The code for the JobHandler singleton is shown in Listing 2, which is pretty straightforward. The only interesting thing about it is the AddJob method that creates a new thread for the job and calls the Start method.
The interface for jobs is shown in Listing 3. The constructor sets up the data set for the job. And the Start method, through each of the airports, calls the web request and stores the returned data in the data set.
A Polling Solution
The first solution to monitoring the weather job is to use polling. To do that the page will post back to itself every two seconds. On the first page request the job will start. After that the page will monitor the output of the weather job by hooking up the data in the job to the data grid on the page. The relationship between the browser, the web server, and the thread is shown in Figure 4.
|Figure 4. Polled: The polled HTML solution shows the relationship between browser, server, and thread.|
The HTML for the polling page is shown in Listing 4. The interesting part is the script block within the refreshScript label. When the label is visible the script will execute to re-submit the form two seconds after the page loads. That will refresh the data in the grid.
The code behind for the polled HTML is shown in Listing 5. The import code here is in the page_load method. If the request ID that is stored in the hidden form field is null or blank then this is the first time the page is loaded. The first time the page is loaded the job is created and the ID of the job is placed in the hidden form field.
The AJAX Solution
The HTTP request is asynchronous. When it completes, the handleResponse function is called. That function parses up the XML and creates some new HTML for the data table. That HTML is placed into the ‘grid’
The code behind for this page is shown in Listing 7. The page_load of this code starts the job and then sets the hidden input field with the URL of the data request page.
The get_data.aspx page takes a request ID and returns an XML representation of the current data set. The page code is shown below:
get_data.aspx<%@ Page language="c#" Codebehind="get_data.aspx.cs" AutoEventWireup="false" Inherits="background.get_data" %>
Obviously the code behind is more important in this case. That code is shown in Listing 8, which starts with setting the content type of the response to ‘text/xml.’ Without that the AJAX code in the browser won’t create an XML document from the response. After that the code gets the request and asks the DataSet to create the XML. It then alters the XML response slightly to add the ‘done’ field, which tells the client whether the request has completed.
When the page first launches it looks like Figure 6.
Threading can be problematic at the best of times. And in this case the threading can be harder to monitor than usual because it’s running on the server in the background. It’s possible that the request will keep running even if there is no web client monitoring it. If that is an issue then you should have the web monitoring code set a time stamp with the threaded process. If the threaded process sees that it hasn’t been viewed for a while then it can cancel itself.
There are a number of ways to do background processing in .NET. This threading approach is just one way. You can also work with ASP.NET cache, or even go so far as to create a real background process.
With these different techniques you can make turn long processing wait times into a user experience that gives rich feedback about how the processing is going.