This post continues the idea that I wrote in Calling page methods from javascript by method name (ASP.NET AJAX) earlier post.
This time I want to use server page methods to query the server from browser about status of some long-running processes.
There are some situations when some processes take too long to complete, send hundreds of emails for example, or perform some complex operations etc. If we don't return the control to the user shortly, UI will not be the user-friendly, user will just wait and look at blank screen trying to guess what is going on on server, and what is the status of current operation.
Actually there are two problems to solve.
1) We need to execute methods for long-running tasks asynchronously on the server. For this we can use additional threads in thread pool or any other asynchronous processing approach(start new thread using ThreadStart delegate, execute method using timer control in System Threading namespace, etc).
2) We should not freeze UI for the user while tasks are in progress. Even if we use asynchronous methods on the server, there will be a moment when task completes and results should be shown to the user. So if we just wait the completion of tasks on the server user will see blank screen.
To go around these problems I want to use the following scheme:
1) We call server-side method from browser when we need to start new long-running process. The method returns without waiting for process completion.
2) We query for statuses of processes being executed on the server from client browser polling server with ajax requests periodically. The time (polling frequency) is to be chosen carefully. If we query the server every half a second, the network with low bandwidth will work very slowly and too much asynchronous calls will be queued by client browser. In the other hand if we choose 10 seconds, the status that is displayed to the user will update less frequently. The time depends on the type of application, type of executing tasks. It is ok to update the status even once in a minute if process takes to complete 1 hour. So this is to choose by software developer, or give this chance to the user - add some control for him to choose the refresh rate.
Completed sample application looks like:
You can see the application online or download source code
I have created a class named ProcessStatus, the class represents one process running on the server, and it contains some basic properties for process, - Name and Status(completion). The code for the class looks like:
namespace Devarchive_net { public class ProcessStatus { public ProcessStatus(string name) { m_Name = name; } private int m_Status = 0; public int Status { get { return m_Status; } set { m_Status = value; } } private string m_Name = ""; public string Name { get { return m_Name; } set { m_Name = value; } } public void IncrementStatus() { lock (this) { m_Status++; } } } }
Only one thing to note here is IncrementStatus method. As you see I surrounded increment operation inside the method with sync lock applied to current instance of the class(this). This way we avoid threading issues and make status modification operation thread-safe. For the simple operation as increment is, you could use the Interlocked class as an alternative.
Here is a class diagram for the ProcessStatus class:
Please remember this two public properties. We will be pleased with some nice surprise later in this article - Page method's result will be serialized to the object using JSON to use in client script. Ok later I will return to this.
The second class is ProcessStatuses:
using System; using System.Collections; using System.Threading; using System.Web; namespace Devarchive_net { public class ProcessStatuses { private const string m_SessionKey = "ProcessStatusesKey"; public static ArrayList Get() { if (HttpContext.Current.Session[m_SessionKey] == null) { HttpContext.Current.Session[m_SessionKey] = new ArrayList(); } return (ArrayList)HttpContext.Current.Session[m_SessionKey]; } public static void StartProcessing(object data) { ProcessStatus process = (ProcessStatus)((object[])data)[0]; while (process.Status < 100) { process.IncrementStatus(); Random rnd = new Random(DateTime.Now.GetHashCode()); Thread.Sleep(((int)(rnd.NextDouble()*40)+10) * 10); } Thread.Sleep(2000); ArrayList.Synchronized((ArrayList)((object[])data)[1]).Remove(process); } } }
The class contains two important methods.
Get() method returns ArrayList object that contains the set of ProcessStatus objects. Get methods is static. It stores collection of process items in Session. This way sets of processes are separated between users.
StartProcessing method is used for imitation of long-running process, in this case for demonstrating purposes I use Thread.Sleep() method to imitate some time-expensive task. I also pass random number of millisecond to the Thread.Sleep method to imitate the different progresses for different threads (in real life it is true, one process may complete faster than another). Last line of StartProcessing method just removes the process object from collection of active processes in a thread-safe manner. The moment when this is done will be after status of a process reaches 100% . Also note - StartProcessing method will be executed in different thread, in order not to lock main thread in which the asynchronous call from client browser will be processed.
StartProcessing method takes parameter of type object, actually we will pass here array of objects - object with index 0 is instance of ProcessStatus class, this is process we are working on, and second - with index 1 - is collection of objects. I am removing process from the collection when job is done.
Now lets create new method in Command class (please see what is the Command class in previous post, I use that architecture to call server methods).
In that class I will add two methods:
/// <summary>
///Launch the new process with
///name specified in object "data"
/// </summary>
/// <param name="data">
///Name of a process
/// </param>
/// <returns>null</returns>
public objectLaunchNewProcess(objectdata)
{
ProcessStatus newProcess =
newProcessStatus(
String.Format(
"{0}, started:{1}",
data, DateTime.Now.ToString("HH:m:ss")
)
);
ArrayList allProcesses = ProcessStatuses.Get();
ArrayList.Synchronized(allProcesses).Add(newProcess);
ThreadPool.QueueUserWorkItem(
newWaitCallback(ProcessStatuses.StartProcessing),
new object[] {newProcess, allProcesses}
);
return null;
}
/// <summary>
///Returns Array of ProcessStatus
///objects to the client script
/// </summary>
/// <param name="data">
///anything, this data
///is not processed in this method
/// </param>
/// <returns>
///Array of ProcessStatus objects
/// </returns>
public objectGetProcessStatuses(objectdata)
{
ArrayList allProcesses = ProcessStatuses.Get();
lock(allProcesses.SyncRoot)
{
returnallProcesses.ToArray();
}
}
The first method LaunchNewProcess creates object of type ProcessStatus and assigns to it name passed from client browser in method parameter (data). It retrieves current set of processes that are executed at the moment, then it adds the newly created process to that set and starts execution of long-running process in a new thread. I use ThreadPool for this purpose, however you can use any asynchronous execution approach, .NET gives us a lot of alternatives here.
Method returns after this. This way the thread of a context where the method executes does not freeze.
The second method - GetProcessStatuses is called from browser every several seconds. The method returns array of ProcessStatuses objects. Remember I said about nice surprise? Now also keep in mind the following fact - the method returns array of ProcessStatuses objects. Lets see later how we use that returned object in javascript.
Lets write client side UI and scripts. Here is a complete code for Default.aspx page:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %> <%@ Register assembly="AjaxControlToolkit" namespace="AjaxControlToolkit" tagprefix="ajaxToolkit" %> <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> <title> Displaying Progress Bar for Long-Running Processes </title> </head> <body> <form id="form1" runat="server"> <asp:ScriptManager EnablePageMethods="true" ID="MainSM" runat="server" ScriptMode="Release" LoadScriptsBeforeUI="true"> <Scripts> <asp:ScriptReference Path="~/Scripts/Main.js" /> </Scripts> </asp:ScriptManager> <script type="text/javascript"> Sys.Application.add_load( applicationLoadHandler ); Sys.WebForms.PageRequestManager.getInstance().add_endRequest( endRequestHandler ); Sys.WebForms.PageRequestManager.getInstance().add_beginRequest( beginRequestHandler ); var mHandlers = {}; mHandlers.Void = function(obj) { ; // nothing to process }; mHandlers.GetStatuses = function() { // call server method // which gets an array // of currently executing processes mainScreen.ExecuteCommand( 'GetProcessStatuses', 'mHandlers.ProcessStatuses', null); setTimeout( "mHandlers.GetStatuses();", parseInt( $get("<%=lblSlider.ClientID %>").innerHTML ) ); }; mHandlers.ProcessStatuses = function(obj) { var resultDiv = $get("resultDiv"); if(obj) { resultDiv.innerHTML = mHandlers.BuildProcessList(obj); } else { resultDiv.innerHTML = " "; } }; mHandlers.BuildProcessList = function(obj) { var i = 0; if (obj.length==0) return " "; var result = "<table " + "cellspacing='0' " + "cellpadding='3' " + "width='99%'>"; result += "<td " + "align='left' " + "style='width:1%; white-space:nowrap'>" + "<b>Process Name</b>" + "</td>"; result += "<td " + "align='left' " + "style='width:89%;'>" + "<b>Progress</b>" + "</td>"; result += "<td " + "align='left' " + "style='width:10%; white-space:nowrap'>" + "<b>Completion</b>" + "</td>"; for (i=0; i<obj.length; i++) { result += "<tr>"; result += "<td " + "align='left' " + "style='width:1%; white-space:nowrap'>" + obj[i].Name + "</td>"; result += "<td " + "align='left' " + "style='width:89%;'>" + "<div " + "style='width:100%; " + "background-color:white' " + ">" + "<div " + "style='width:" + obj[i].Status + "%; background-color:" + (obj[i].Status < 100 ? "red" : "green") + ";'>" + " </div>" + "</div>" + "</td>"; result += "<td " + "align='left' " + "style='width:10%; white-space:nowrap'>" + obj[i].Status + " %" + "</td>"; result += "</tr>"; } result += "</table>"; return result; }; </script> <div> Click "Start new process" several time and update refresh speed to see status of server-side processes real-time </div> <br /><br /> <div> Process Name: <input type="text" id="processName" value="File Download" /> <input type="button" value="Start new process" onclick=" mainScreen.ExecuteCommand( 'LaunchNewProcess', 'mHandlers.Void', $get('processName').value); " /> <br /> <br /> </div> <div> <br /> <div style="float:left; width:100px;"> Update speed: </div> <div style="float:left; width:200px;"> <ajaxToolkit:SliderExtender ID="seUpdateSpeed" runat="server" BehaviorID="tbSlider" TargetControlID="tbSlider" BoundControlID="lblSlider" Orientation="Horizontal" Minimum="500" Maximum="5000" Steps="10" EnableHandleAnimation="true" TooltipText="Slider: value {0}. Please slide to change value." /> <asp:TextBox ID="tbSlider" runat="server" style="right:0px" Text="500" /> </div> <div style="float:left"> <asp:Label ID="lblSlider" runat="server" Text="500" /> Milliseconds</div> </div> <br /> <br /> <br /> <div style="border: dashed 1px black;" id="resultDiv"> </div> </form> </body> </html>
In short here we call LaunchNewProcess server method when new process should be launched (button click), and we call GetProcessStatuses server method periodically to get processes and their statuses from the server. When server returns results of second call, I just dynamically create HTML and show it to the user.
The main important thing here - is the surprise I was talking about above. note how I access object returned from the server - "if (obj.length==0)". So actually from javascript we see the object as array ! This means we returned the array from server and AJAX framework serialized this object into array in js, this is great isn't it? It is not all. See how I access the properties of each object in array : obj[i].Name, obj[i].Status. This means - we returned array of ProcessStatus objects from server, and they are serialized into similar objects on client side !
Also I have modified the Main.js file (See full listing of the file in previous post). I modified Init function:
mainScreen.Init = function() { /// <summary> /// Initializes mainScreen variables /// </summary> setTimeout("mHandlers.GetStatuses();", 100); };
As you see I just initiate call to GetStatuses function first time, after this it calls itself periodically using setTimeout function.
To see whole picture download the source code, and see it live !
Hope this helps.



10 comments:
Nice article but some things are escaping me - I am pretty new at this!
How should I hook this up to real processes like functions to send some Email or doing database stuff.
How would I return and display status text from that process.
Thanks
Bill
Hi Bill,
The most important code line is :
ThreadPool.QueueUserWorkItem(
newWaitCallback(ProcessStatuses.StartProcessing),
new object[] {newProcess, allProcesses}
);
the line is in LaunchNewProcess method.
So what it does -
It uses ThreadPool.QueueUserWorkItem method to start static method ProcessStatuses.StartProcessing in a new thread of ThreadPool.
So my answer will be - you should make long-running database calls, send emails etc in StartProcessing static method of ProcessStatuses class.
At the moment it looks like this:
public static void StartProcessing(object data)
{
ProcessStatus process = (ProcessStatus)((object[])data)[0];
while (process.Status < 100)
{
process.IncrementStatus();
Random rnd = new Random(DateTime.Now.GetHashCode());
Thread.Sleep(((int)(rnd.NextDouble()*40)+10) * 10);
}
Thread.Sleep(2000);
ArrayList.Synchronized((ArrayList)((object[])data)[1]).Remove(process);
}
But - actual implementation of the method is up to you, Instead of calling Thread.Sleep - you can send several emails. For example if there is 3000 emails to send, 3000/100 = 30 emails will reflect one percent of process completeion, so you could send 30 emails and then update process status incrementing it's completion percent by one.
But this is only one approach among many.
If you need to read or write very large file to disk, or communicate over the network with remote machines to send/recieve some data - .NET framework gives us Asynchronous Programming Model ready to use. With asunchronous programmiung model you can poll for a status of executing process using "Polling Model" :
// -------------------
// C#
byte[] buffer = new byte[100];
string filename =
string.Concat(Environment.SystemDirectory, "\\mfc71.pdb");
FileStream strm = new FileStream(filename,
FileMode.Open, FileAccess.Read, FileShare.Read, 1024,
FileOptions.Asynchronous);
// Make the asynchronous call
IAsyncResult result = strm.BeginRead(buffer, 0, buffer.Length, null, null);
// Poll testing to see if complete
while (!result.IsCompleted)
{
// Do more work here if the call isn't complete
Thread.Sleep(100);
}
// Finished, so we can call EndRead and it will return without blocking
int numBytes = strm.EndRead(result);
// Don't forget to close the stream
strm.Close();
Console.WriteLine("Read {0} Bytes", numBytes);
Console.WriteLine(BitConverter.ToString(buffer));
// -------------------
here using result.IsCompleted property you can find out what is a status of asynchronous operation.
and more thing - Thread pool is for relatively short tasks, you should use ThreadStart Delegate to launch really long running process.
Hope this helps,
Kirill
Got it!
But I would also like to return status text from the server - eg "sending Email to Bill"
Any suggestions?
Thanks
Bill
Yes actually the ProcessStatus class is used for this - it is nothing more then a object holding a status of a process.
In the sample it defines two properties -
Status - integer that explains what is percentage of task completion,
Name - the description of the process - this can be the string "Sending emails to subscribed members"
You can easily add another property, CurrentActivity(string) for example, which will reflect current activity of a process("Sending email to subscriber No719"). Then you should modify it's value from running thread in thread-safe manner. Please see how to do this on MSDN documentation.
The Idea is:
You have the method running in the thread, you have ProcessStatus object passed into the method, so ProcessStatus object can be modified from the method in any desirable way.
Same time the client browser is requesting the array of ProcessStatus objects (also in thread-safe manner), so browser gets the statuses and displays them to the user independently from the running threads.
And threrefore you can change the status class structure to hold more properties determining the state of a process, and you can analyze/display that object from javascript.
Thanks.
Kirill
Thanks very much for the article!
Isn’t it possible to make a stop button that stops the working progress?
//Peter
I modified your code as follows:
public static void StartProcessing(object data)
{
ProcessStatus process = (ProcessStatus)((object[])data)[0];
while (process.Status < 100)
{
process.IncrementStatus();
RunMyProcess();
}
ArrayList.Synchronized((ArrayList)((object[])data)[1]).Remove(process);
}
I only need RunMyProcess() to run once but the code as it is now runs it 100 times. How should I fix this code, to run once only?
Brilliant, Thank you this blog helped me more to learn.
I have also updated some content about displaying progress bar yesterday in my blog but using XMLHttpRequest and DHTML.
Cheers,
Srinivas
Excellent articale, this saved me alot of time and effort.
Thanks
Matt
Hi Krill
Great article.. But i have a query this progress bar is not working fine with Mozilla Firefox browser . Could you please send back to me if any solution for the same. Appreciate your quick
responses
Thanks
Sarath
Hi,
The sample works in Firefox and IE just fine, here is a live example:
http://www.devarchive.net/displaying_progress_bar_for_long_running_processes.aspx
Post a Comment