ASP.NET Developer Techniques for Dealing With Long Running Processes

Introduction

In most ASP.NET Web Applications interaction with the user is fairly simple and can be accomplished within the normal page life cycle. The client web browser dictates the amount of time allowed which is normally 30 seconds, so any processing must be accomplished within that period. Lets say your process takes 15 seconds, you could simply perform the process during the Page_Load or another Event. However, causing the user to wait 15 seconds could lead to user frustration and clicking multiple times. Ideally you want to minimize the amount of time used for the page life cycle to keep your web application responsive. So when you have a long running process you have conflicting needs, both to stay responsive to the user and to provide sufficient time to complete your task. To satisfy both needs we need a happy medium to stay responsivle and complete the task.

When you make the leap to perform a long running tasks in the background we need to design the page in a manor to allow the user to launch the process, monitor the process and see the results. To get started we will create a page which will be used to perform these three tasks.

To simplify the process we will create a single ASP.NET AJAX page to perform these three functions. Three different functions will be grouped into 3 separate views withing a multiview control. The multiview control is contained with an update panel which will be used to provide seemless feedback to the user as shown in the figure below.



Figure 1 – Page Layout

The first of the three views above simply provides the mean to start the process. The second view contains an ASP.NET AJAX loading image, status text and an AJAX timer. And finally the third shows completion. The markup for these views is listed below.


<asp:ScriptManager ID=”ScriptManager2″ runat=”server”>
</asp:ScriptManager>
<asp:UpdatePanel ID=”UpdatePanel2″ runat=”server”>
  <ContentTemplate>
     <asp:MultiView ID=”mvvProcess” runat=”server” ActiveViewIndex=”0″>
        <asp:View ID=”vLaunch” runat=”server”>
           <asp:Button ID=”Button1″ runat=”server” onclick=”Button1_Click” Text=”Start” />
        </asp:View>
        <asp:View ID=”vProgress” runat=”server”>
           <asp:Image ID=”Image1″ runat=”server” ImageUrl=”ajax-loader.gif” />
           <asp:Label ID=”Label1″ runat=”server” Text=”Starting”></asp:Label>
           <asp:Timer ID=”timUpdate” runat=”server” Enabled=”False” Interval=”1000″ ontick=”timUpdate_Tick”> </asp:Timer>
        </asp:View>
        <asp:View ID=”vComplete” runat=”server”>
           <asp:Label ID=”Label2″ runat=”server” Text=”Complete”></asp:Label>
        </asp:View>
     </asp:MultiView>
  </ContentTemplate>
</asp:UpdatePanel>

Next we jump into the code behind the first view and the Start button. The click event handler for the Start button is listed below:


protected void Button1_Click(object sender, EventArgs e)
{
  //Start the Process
  lock (Session.SyncRoot)
  {
     Session[“complete”] = false;
     Session[“status”] = “”;
  }

  Thread t = new Thread(new ParameterizedThreadStart(ThreadProcess));
  t.Start(Session);

  //Switch the View
  mvvProcess.SetActiveView(vProgress);
  timUpdate.Enabled = true;
}


The Start button click event handler first sets a complete and status session values to the desired inital values. You’ll notice we are locking the Session object. The purpose behind this is to prevent conflicts and/or the possibility for corrupting Session when the thread is running. Next we create a new Thread and give it the handle to a ThreadProcess method. Then we start the thread and pass in the Session object to the thread. You may be thinking why the Thread needs Session since the method is in the code behind. The answer is simple, Session is tied to the life-cycle of the page, so while the page is active Session is available. But when the page is not being processed the Session object is invalid. So in order for the Thread to access Session and save provide status updates back to the user we need to give it a handle. Finally the button click sets the active view and enables the AJAX update timer. The next code block is for the ThreadProcess method used to perform the background process.


private void ThreadProcess(object data)
{
  System.Web.SessionState.HttpSessionState s = (System.Web.SessionState.HttpSessionState)data;

  int i;
 
  for (i = 0; i < 30; i++)
  {
     //Update the status for the user
     lock (s.SyncRoot)
     {
        s[“status”] = “Running: ” + i.ToString();
     }

     //Add some delay to simulate work
     System.Threading.Thread.Sleep(1000);
  }

  lock (s.SyncRoot)
  {
     s[“complete”] = true;
  }
}


The ThreadProcess method first grabs the reference to Session passed into the method. Then it performs a simple loop for 30 times, updates session status each time, putting the thread to sleep for 1 second each time. After finishing the loop the session complete status is updated and the thread terminates. Note, again we are locking session each time it is accessed to prevent collisions with the main page.

The next code block is for the AJAX Update timer Tick event.


protected void timUpdate_Tick(object sender, EventArgs e)
{
  Label1.Text = Session[“status”].ToString();

  if (Session[“complete”] as bool? == true)
  {
     timUpdate.Enabled = false;
     mvvProcess.SetActiveView(vComplete);
  }
}


This event handler first updates the status and checks to see if the session complete flag has been set. If the session complete flag is set, it will then disable the update timer and set the multiview to the Complete view. After pressing the Start button, the user should see the status change from Running 1 thru Running 30 and finally to Complete as shown by the following 3 images.



Figure 2 – Start View



Figure 3 – Running View



Figure 4 – Complete View

Conclusion

While this is effective and does provide a simple method for performing long running background process it does have its disadvantages. Using Session for communication does add overhead, not to mention the other drawbacks related to Session including scalability and memory usage. Since this example used Session we also need to lock the Session object to prevent collisions and/or corruption this again adds overhead. As an alternative to using Session you could also use a Database table for status information. While this eliminates the Session and locking overhead it does mean additional hits to a table; however, it eliminates the scalability issues associated with Session.

In addition to the alternative for Session we can also look at different ways of background threading. The most obvious is to take advantage of the ThreadPool. Simply put, we could add an item to the ThreadPool and allow it to perform the work. While at first this may sound like a good idea; in ASP.NET the ThreadPool serves a very important role. In fact the ThreadPool is used for processing all requests from the user. So if you were to start tying up the ThreadPool with background work you could eventually cause the application to become unresponsive. Another method you could use is to perform a JavaScript call to a web service/method which could perform the process; however, this method does not allow you to provide feedback to the user until the request either fails or completes.

It is also important to note that during the second step in the process above, the AJAX timer is being triggered once per second which causes the UpdatePanel to refresh and the code behind to be executed. In a production situation it more realistic to increase the Timer interval value to 3 to 5 seconds to reduce the number of hits to the server.

There are alternative methods for executing background tasks within ASP.NET, the simple technique used above is just that simple. There is very little to setup and easy to learn, making it quick to implement and support in your next project.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read