在C#中执行某项任务时如何显示进度条?

26

我想在执行某些工作时显示进度条,但这会挂起用户界面并且进度条不会更新。

我有一个带有ProgressBar的WinForm ProgressForm,它将以跑马灯的方式无限期地继续。

using(ProgressForm p = new ProgressForm(this))
{
//Do Some Work
}
现在有许多解决问题的方法,比如使用BeginInvoke,等待任务完成并调用EndInvoke。或者使用BackgroundWorkerThreads
虽然我在使用EndInvoke时遇到了一些问题,但这不是我的问题。问题是在处理这种情况时,您使用的最佳和最简单的方法是什么,即当您必须向用户显示程序正在工作且未响应时,如何使用最简单的代码来处理它,以及如何更新GUI,而不会泄漏任何信息,并且高效率。
例如,BackgroundWorker需要有多个函数、声明成员变量等。此外,您需要保留对ProgressBar表单的引用并将其处理掉。 编辑:BackgroundWorker不是答案,因为可能无法得到进度通知,这意味着没有调用ProgressChanged,因为DoWork只调用一个外部函数,但我需要继续调用Application.DoEvents();以使进度条继续旋转。
赏金是为了找到解决此问题的最佳代码解决方案。我只需要调用Application.DoEvents(),以便Marque进度条可以工作,而工作函数在主线程中工作,并且它不返回任何进度通知。我从来没有需要.NET魔法代码自动报告进度,我只需要比以下更好的解决方案:
Action<String, String> exec = DoSomethingLongAndNotReturnAnyNotification;
IAsyncResult result = exec.BeginInvoke(path, parameters, null, null);
while (!result.IsCompleted)
{
  Application.DoEvents();
}
exec.EndInvoke(result);

保持进度条活动状态(即不冻结但可以刷新滚动条)


这是哪个版本的 .NET? - Chris S
2
@Priyank - 不管你使用 BackgroundWorker 还是什么方式,如果你没有获取到进度更新通知,你打算怎样更新进度条? - Paolo
@Paolo:它是Marque,无限旋转,我只想让用户知道正在进行一些处理。 - Priyank Bolia
2
如果您的进度条只是在等待后台任务完成之前不停旋转,为什么要费力处理所有这些复杂性呢?只需在 UI 线程上运行动画:如果使用任何合理的方法启动后台任务,则后台任务将不会阻塞 UI。在完成通知时终止动画即可。 - Godeke
@Godeke:我想在主线程上运行所谓的后台任务,而不使用任何合理的方法,如线程、BackgroundWorker、SyncronizationContext。 - Priyank Bolia
13个回答

0
阅读您的要求,最简单的方法是显示一个无模式窗体,并使用标准的System.Windows.Forms计时器来更新无模式窗体上的进度。没有线程,也没有可能的内存泄漏。
由于这只使用了一个UI线程,在主处理过程的某些点上,您还需要调用Application.DoEvents()来保证进度条在视觉上得到更新。

0

关于您的编辑。

您需要一个BackgroundWorker或Thread来完成工作,但它必须定期调用ReportProgress()方法告诉UI线程它正在做什么。DotNet不能神奇地计算出您已经完成了多少工作,因此您必须告诉它(a)您将达到的最大进度量,然后(b)在过程中约100次左右告诉它您完成了哪个进度量。(如果您报告的进度少于100次,进度条将会跳跃式移动。如果您报告的进度超过100次,您只会浪费时间尝试报告比进度条更详细的信息)

如果您的UI线程可以在后台工作时继续运行,那么您的工作就完成了。

然而,在大多数需要运行进度指示的情况下,您的UI需要非常小心以避免重入调用。例如,如果您在导出数据时运行进度显示,您不希望用户在导出正在进行时再次开始导出数据。

您可以通过两种方式处理这个问题:

  • 导出操作会检查后台工作程序是否正在运行,并在其导入时禁用导出选项。这将允许用户在程序中做任何事情,除了导出 - 如果用户可以(例如)编辑正在导出的数据,则仍然可能存在危险。

  • 将进度条作为“模态”显示运行,以便在导出过程中您的程序保持“活动状态”,但用户实际上不能做任何事情(除了取消),直到导出完成。DotNet在支持此方面非常糟糕,尽管这是最常见的方法。在这种情况下,您需要将UI线程放入忙等待循环中,其中调用Application.DoEvents()以保持消息处理运行(因此进度条将起作用),但您需要添加一个MessageFilter,仅允许您的应用程序响应“安全”的事件(例如,它将允许Paint事件,以便您的应用程序窗口继续重绘,但它将过滤掉鼠标和键盘消息,以便用户在导出进行时实际上无法在程序中执行任何操作。还有一些巧妙的消息需要通过才能使窗口正常工作,找出这些消息需要几分钟时间 - 我在工作中有一个列表,但我恐怕手头没有它们。这些都是明显的消息,如NCHITTEST加上一个狡猾的.NET消息(恶意在WM_USER范围内),这对于使其正常工作至关重要。

最后一个棘手问题与糟糕的dotNet进度条有关,当你完成操作并关闭进度条时,你会发现它通常在报告"80%"这样的值时退出。即使你强制将它设置为100%,然后等待大约半秒钟,它仍然可能无法达到100%。烦死了!解决方案是将进度设置为100%,然后设置为99%,然后再设置为100% - 当进度条被告知向前移动时,它会缓慢地向目标值进行动画处理。但是,如果您让它"向后"走,它会立即跳到该位置。因此,在最后一刻将其反转,您可以获得实际显示所要显示的值的进度条。

我只需要调用Application.DoEvents(),这样Marque进度条就能工作了,同时工作函数在主线程中运行,并且不返回任何进度通知。我从来没有需要.NET魔法代码自动报告进度,我只需要比BeginInvoke和EndInvoke更好的解决方案,以保持进度条活动(意味着不会冻结但刷新Marque)。 - Priyank Bolia

0
如果你想要一个“旋转”的进度条,为什么不将进度条样式设置为“跑马灯”,并使用BackgroundWorker来保持UI的响应性呢?使用“跑马灯”样式比实现旋转进度条更容易...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接