在Cancel取消Example的页面进度中

分享于 

10分钟阅读

Web开发

  繁體

Sample Image - import-running.jpg

介绍

多年来,我编写了几个允许用户启动长期运行流程的应用程序。 对于 1或者 2次操作,可以离开沙漏,但是要更长的时间,需要进行进度显示。 在桌面界面中,标准进度显示是带有进度表和大fat取消按钮的屏幕或者对话框。 通常,对话框上有一些空间来显示一些文本。图形或者它的他计算机工作的表示形式。 围绕进度窗口的两个典型应用程序示例是 Windows 安装程序和 Windows 磁盘碎片整理程序。

我寻找了一种方法来为我的ASP.NET 应用程序创建一个类似。 我发现进度表进程对话,但没有真正绑定到所有的。 这是一个页面的例子。 这里设计的一些亮点:

  • ASP.NET 2.0.
  • 单个页面启动进程,显示进程,并显示进程完成消息。 我觉得这个设计对于我的UI和编程风格非常适合。
  • 跨浏览器 兼容( 我试过 IE,Firefox 和 Opera )。
  • 轻松使用 CSS。
  • 无 JavaScript。

用户界面设计

一些屏幕截图展示了示例用户界面:

本示例UI是一个简单的。修剪的屏幕版本,特定于我工作的项目。 这是一个MLS对象的编辑器屏幕。 可以看到,我已经将导入列表函数集成到MLS编辑器屏幕中了。 对于导入列表,我可以有单独的屏幕( 或者标签),但是对于这个应用程序,我喜欢在一个页面上拥有一个MLS的所有东西。

示例代码高亮显示

我发现,良好的进度显示取决于所涉及的过程。 一些进度显示只需要一个简单的文本字符串就可以摆脱。 有些可以对剩余时间给出很好的估计。 有些根本估计不出来。 因此,我把我的示例代码更像是一个设计 Pattern ( 和示例),而不是可重用的组件。 如果你希望具有固定的功能,可以重用的进度对话框,那么应该是一个相对简单的。

要讨论代码,让我们从底部开始,然后开始。 第一个类/文件是 JobQueueEventArgs。 在我的许多应用程序中,长流程由作业队列处理。 作业被某些类型的初始化例程放置在作业队列中。 然后,一组工作线程通过更新全局 JobQueueEventArgs 对象并触发 JobQueueEvent,从队列中删除作业。执行作业并更新进度显示。

在这里应用程序中,每次完成作业时,工作线程都会添加作业到 JobQueueEventArgs.TimeCompleted的时间:

for (int i = 0; i < importedListings; i++)
{
 DateTime start = DateTime.Now;
 // Remove job from queue.. . run job _importStatus.JobsCompleted++;
 _importStatus.JobsRemaining--;
 _importStatus.TimeCompleted += DateTime.Now - start;
}

剩下的时间是通过计算每个作业的平均时间来估算剩下的作业数。 我们将所有这些线程的数量除以运行的线程,因为两个线程应该完成两次?

private TimeSpan _timeCompleted = TimeSpan.Zero;public TimeSpan TimeCompleted
{
 get { return _timeCompleted; }
 set 
 { 
 // MT: Obviously, this can get a little off _timeCompleted = value;
 _timeRemaining = new TimeSpan(0, 0, 
 (int)((_timeCompleted.TotalSeconds * _jobsRemaining)/
 _jobsCompleted)/_threadsRunning);
 }
}

我这里没有锁但是应该是可以的。 这是进度信息,不是银行账户余额的银行。

我不认为所有忙碌的工作都是为了一个真正的作业队列,因这里我让 MLS.ImportListings 模拟一个工作线程。 另外,由于 ASP.NET 应用程序必须轮询用户的状态( 当页面位于浏览器中时,它无法从作业队列接收事件),所以我不会麻烦创建和触发实际事件。 如果 MLS 在GUI应用程序中被使用,那么你需要添加这个代码。 ASP.NET 应用程序通过读取 MLS.ImportStatus 属性来轮询状态。 此外,在 MLS 中还有一些简单的属性( MLSIDMLSListingsURL ) 和方法( InsertUpdate ) 来模拟典型的业务对象。 为了支持取消操作,发生了两种情况:

  • MLS.CancelImport 设置一个标志:
publicvoid CancelImportListings()
{
 _importCanceled = true;
}
  • MLS.ImportListings 尽可能频繁地检查这里标志:
for (int i = 0; i < importedListings; i++)
{
 if (_importCanceled)
 {
 _importStatus.Status = JobQueueEventArgs.StatusType.Idle;
 _importStatus.QueueMessage = "MLS Listings Import canceled by user.";
 return;
 }
 // Remove job from queue.. . run job}

ProgressBar 是显示进度表的控件。 我从这个 CodeProject文章的ProgressBar开始。 我主要使用流布局,所以我更改了控件以使用 width=" 100%"创建它的table。 行高度是由属性或者它的内容( 像一个图像) 设置的。 对于增加浏览器兼容性( Firefox ),& nbsp,总是在单元格内容中使用。

Default.aspx 是一个示例页面。 大部分代码是业务对象的典型 ASP.NET 代码。 以下是操作开始的地方:

protectedvoid ImportListings_Click(object sender, EventArgs e)
{
 MLS mls = GetMLS();
 PageToMLS(mls);
 mls.Update();
 Session[_importSessionKey] = mls;
 mls = (MLS)Session[_importSessionKey];
 Thread thread = new Thread(new ThreadStart(mls.ImportListings));
 thread.Start();
 UpdateControls();
 UpdateProgress();
 AddMetaRefresh();
}

这看起来很简单,而且大多数是,但是有一些事情需要注意:

你可以看到,MLS 对象被添加到会话中。 what的是,这只适用于数据库的默认sessionState方式。 其他同态模式要求对对象进行序列化和反序列化。 虽然可以序列化 MLS,但不能序列化运行的线程( ),因此任何反序列化的对象都不会得到状态更新。 如果不能使用 arraylist ( 说因为你在服务器场上运行你的应用程序),则需要将 ImportListing 函数移动到一个单独的进程中,然后执行一些类似SOAP的工作。 考虑 3层架构。

ImportListings_Click 中另一个有趣的事情是对 AddMetaRefresh的调用。 以下是发生的情况:

protectedvoid AddMetaRefresh()
{
 MLS mls = (MLS)Session[_importSessionKey];
 int refreshSeconds = mls!= null && 
 mls.ImportStatus.TimeRemaining.TotalSeconds >10? 5 : 1;
 Literal metaRefresh = new Literal();
 metaRefresh.Text = string.Format("<meta http-equiv="refresh" content="{0}">", 
 refreshSeconds);
 Header.Controls.Add(metaRefresh);
}

你可能会有其他的方法来做这个。 这里 MSDN文章使用 JavaScript ( 我认为因为作者被卡住了,你想要传递一个 URL。)。 我发现"元刷新"方法最干净。 根据需要减少刷新请求速率,可以根据需要调整刷新秒数。 这种算法可以被精细化,但估计时间通常相当粗糙,没有意义,你不想等太长。 创建 Literal 控件并将它的添加到 Header.Conrols.Add 中很不错,因为该页面并不总是需要"元刷新"标记。 此外,当 Default.aspx 更改为使用 ASP.NET 母版页时,这里代码仍将工作。

那么,取消工作是如何? 下面是代码:

protectedvoid CancelImportListings_Click(object sender, EventArgs e)
{
 MLS mls = (MLS)Session[_importSessionKey];
 if (mls!= null)
 {
 mls.CancelImportListings();
 UpdateProgress();
 if (mls.ImportStatus.Status == JobQueueEventArgs.StatusType.Idle)
 Session[_importSessionKey] = null;
 else AddMetaRefresh();
 }
 UpdateControls();
}

如果对 mls.CancelImportListings的调用总是立即工作,那么通常我们必须等待所有工作线程关闭。 因此,除非所有线程都立即停止( ImportStatusIdle ),我们调用 AddMetaRefresh,让浏览器保持刷新直到导入被取消。

运行演示

在 Visual Studio 2005中将项目作为网站打开,并在不调试的情况下按Ctrl+F5启动。 首先,你将看不到导入列表按钮,因为编辑屏幕处于插入模式,因这里只能导入( 真正的真实世界让一切变得如此复杂)。 在更新模式(。查看导入列表按钮) 中模拟编辑屏幕,将查询字符串"MLSID=1"附加到 URL。 完整的URL应该类似于下面这样:

http://localhost:37519/InPageProgressWithCancel/Default.aspx?MLSID=1

CAN  进度