
3.4 真实进度的呈现:使用Timer与UpdatePanel控件
倘若某一个Async-Postback动作需要花费较多时间来处理,例如批次新增或转文件等作业,客户多半不喜欢网页就此停滞,而希望网页能在运行期间定时回报目前的处理进度。以转档作业为例,以往实现这类需求时,多半是将整个转档动作放到Thread(线程)中,并产生一个工作ID,存放在事先创建于网页中的Hidden Field内,用来作为访问Cache信息的键值,当Thread运行时,会依据此键值将进度填入Cache中,网页则是利用JavaScript的setTimeout函数,定时做Postback,再在Server端所送上来的Hidden Field中取得工作ID,进而取出Cache中的进度信息显示。时至今日,这种处理进度回报的流程仍然可行,不过有了UpdatePanel及Timer等控件的协助,定时Postback的动作便可轻松升级为定时Async-Postback动作,让客户不会再有网页画面闪动的不舒服感受,请照着下列步骤做。
1. 创建一个新网页,命名为AsyncPostbackProgressReport.aspx。
2. 在页面中放入ScriptManager控件。
3. 放入UpdatePanel控件,命名为UpdatePanel1。
4. 将UpdatePanel1控件的UpdateMode属性设为Conditional。
5. 放一个Timer控件至UpdatePanel1控件中,命名为Timer1,设定Interval属性为2000,也就是每两秒做一次Async-Postback动作。
6. 设定Timer1控件的Enabled属性为False。
7. 放一个Button控件至UpdatePanel1控件中,命名为Button1,Text设为Run。
8. 放一个Label控件至UpdatePanel1控件中,命名为Label1,Text属性设为空白。
9. 放一个Button控件至UpdatePanel1控件中,命名为Button2,Text设为Cancel。
10. 在Timer1控件的Click事件中键入程序3-10中Timer1_Tick函数内的代码。
11. 在Button1控件的Click事件中键入程序3-10内Button1_Click函数中的代码。
12. 在Button2控件的Click事件中键入程序3-10内Button2_Click函数中的代码。
程序3-10
Samples\3\AjaxDemo1\AsyncPostbackProgressReport.aspx.cs using System; using System.Data; using System.Configuration; using System.Collections; using System.Web; using System.Web.Security; using System.Web.UI; using System.Web.UI.WebControls; using System.Web.UI.WebControls.WebParts; using System.Web.UI.HtmlControls; using System.Threading; public partial class AsyncPostbackProgressReport : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } protected void Timer1_Tick(object sender, EventArgs e) { //由Cache中取出进度信息 TaskInformation info = Cache[TaskID_Hidden.Value] as TaskInformation; if (info != null) Label1.Text = string.Format("Processing {0}%", info.CurrentProgress); else { Label1.Text = string.Empty; Button1.Enabled = true; Timer1.Enabled = false; Button2.Visible = false; } } protected void Button1_Click(object sender, EventArgs e) { Thread th = new Thread(new ParameterizedThreadStart(Work)); //产生一个GUID,放到Hidden Field中,作为取得回报进度信息的键值 TaskID_Hidden.Value = Guid.NewGuid().ToString(); //创建TaskInformation对象,并将其放入Cache中。 TaskInformation info = new TaskInformation(); info.CurrentProgress = 0; Cache[TaskID_Hidden.Value] = info; th.IsBackground = true; Timer1.Enabled = true; Button1.Enabled = false; Button2.Visible = true; Label1.Text = "Processing"; th.Start(TaskID_Hidden.Value); } private void Work(object state) { for (int i = 0; i < 10; i++) { System.Threading.Thread.Sleep(2000); //定时更新Cache中的进度信息对象,如果Cacnel被设为True,那么立即停止工作 TaskInformation info = Cache[(string)state] as TaskInformation; if (info.Cacnel) break; info.CurrentProgress = i * 10; Cache[(string)state] = info; } //工作完成后,移除Cache中的进度信息对象 Cache.Remove((string)state); } protected void Button2_Click(object sender, EventArgs e) { //取消时,将Cache中的TaskInformation之Cancel设为True。 TaskInformation info = Cache[TaskID_Hidden.Value] as TaskInformation; info.Cacnel = true; Button2.Visible = false; Label1.Text = "Canceling...."; } } [Serializable] public class TaskInformation { public int CurrentProgress; public bool Cacnel; }
图3-9是运行时期的画面。

图3-9
关于进度回报
使用Timer控件实现进度回报并不算是个好主意,因为它会一直运行Async-Postback动作,每次都会送出整个页面的ViewState到Server端,这是相当低效率的手法,后面的章节会采用PageMethods/Web Servcies等较高效率的手法完成同样的工作。