我想在做一些工作时显示进度条,但这会挂起 UI,并且进度条不会更新。
我有一个带有 ProgressBar
的 WinForm ProgressForm,它将以字幕 方式无限期地继续。
using(ProgressForm p = new ProgressForm(this))
{
//Do Some Work
}
现在有很多方法可以解决这个问题,比如使用BeginInvoke
,等待任务完成再调用EndInvoke
。或者使用 BackgroundWorker
或 Threads
。
我在使用 EndInvoke 时遇到了一些问题,尽管那不是问题所在。问题是你用来处理这种情况的最好和最简单的方法是什么,你必须向用户展示程序正在运行而不是无响应,以及你如何使用最简单的代码来处理这种情况是有效的并且不会' t 泄漏,并且可以更新 GUI。
像 BackgroundWorker
需要有多个函数,声明成员变量等。您还需要持有对 ProgressBar Form 的引用并处理它。
编辑:BackgroundWorker
不是答案,因为我可能没有收到进度通知,这意味着不会调用 ProgressChanged
因为 DoWork
是对外部函数的单个调用,但我需要继续调用 Application.DoEvents();
以使进度条保持旋转.
赏金是针对此问题的最佳代码解决方案。我只需要调用 Application.DoEvents()
这样 Marque 进度条就会工作,而 worker 函数在主线程中工作,并且它不会返回任何进度通知。我从来不需要 .NET 魔术代码来自动报告进度,我只需要一个比以下更好的解决方案:
Action<String, String> exec = DoSomethingLongAndNotReturnAnyNotification;
IAsyncResult result = exec.BeginInvoke(path, parameters, null, null);
while (!result.IsCompleted)
{
Application.DoEvents();
}
exec.EndInvoke(result);
使进度条保持事件状态(意味着不卡住但刷新品牌)
最佳答案
在我看来,您的操作至少基于一个错误的假设。
1。您无需引发 ProgressChanged 事件即可获得响应式 UI
在你的问题中你这样说:
BackgroundWorker is not the answer because it may be that I don't get the progress notification, which means there would be no call to ProgressChanged as the DoWork is a single call to an external function . . .
实际上,是否调用ProgressChanged
事件并不重要。该事件的全部目的是暂时将控制权转移回 GUI 线程以进行更新,以某种方式反射(reflect) BackgroundWorker
正在完成的工作的进度。 如果您只是简单地显示选取框进度条,那么引发 ProgressChanged
事件实际上毫无意义。只要进度条显示,它就会继续旋转,因为BackgroundWorker
正在与 GUI 分开的线程上执行其工作。
(附带说明,DoWork
是一个事件,这意味着它不只是“对外部函数的一次调用”;您可以添加任意多个处理程序;并且每个处理程序都可以包含任意数量的函数调用。)
2。您无需调用 Application.DoEvents 即可获得响应式 UI
在我看来,您似乎认为 唯一 GUI 更新的方法是调用 Application.DoEvents
:
I need to keep call the Application.DoEvents(); for the progress bar to keep rotating.
在多线程场景下情况并非如此;如果您使用 BackgroundWorker
,当 BackgroundWorker
执行附加到其 DoWork
的任何操作时,GUI 将继续响应(在其自己的线程上) > 事件。下面是一个简单示例,说明这对您有何帮助。
private void ShowProgressFormWhileBackgroundWorkerRuns() {
// this is your presumably long-running method
Action<string, string> exec = DoSomethingLongAndNotReturnAnyNotification;
ProgressForm p = new ProgressForm(this);
BackgroundWorker b = new BackgroundWorker();
// set the worker to call your long-running method
b.DoWork += (object sender, DoWorkEventArgs e) => {
exec.Invoke(path, parameters);
};
// set the worker to close your progress form when it's completed
b.RunWorkerCompleted += (object sender, RunWorkerCompletedEventArgs e) => {
if (p != null && p.Visible) p.Close();
};
// now actually show the form
p.Show();
// this only tells your BackgroundWorker to START working;
// the current (i.e., GUI) thread will immediately continue,
// which means your progress bar will update, the window
// will continue firing button click events and all that
// good stuff
b.RunWorkerAsync();
}
3。不能在同一个线程上同时运行两个方法
你这样说:
I just need to call Application.DoEvents() so that the Marque progress bar will work, while the worker function works in the Main thread . . .
您要求的根本不是真实的。 Windows 窗体应用程序的“主”线程是 GUI 线程,如果它忙于长时间运行的方法,则不会提供视觉更新。如果您不这么认为,我怀疑您误解了 BeginInvoke
的作用:它在单独的线程 上启动委托(delegate)。事实上,您在问题中包含的用于在 exec.BeginInvoke
和 exec.EndInvoke
之间调用 Application.DoEvents
的示例代码是多余的;您实际上是从 GUI 线程重复调用 Application.DoEvents
,无论如何都会更新。 (如果您发现其他情况,我怀疑是因为您立即调用了 exec.EndInvoke
,它阻塞了当前线程,直到该方法完成。)
是的,您正在寻找的答案是使用 BackgroundWorker
。
您可以使用BeginInvoke
,而不是从 GUI 线程调用 EndInvoke
(如果方法未完成,它将阻止它),将 AsyncCallback
参数传递给您的 BeginInvoke
调用(而不是仅仅传递 null
),并在您的回调中关闭进度表单。但是请注意,如果您这样做,您将不得不从 GUI 线程调用关闭进度表单的方法,否则您将尝试关闭一个表单,这是一个 GUI 函数,从非 GUI 线程。但实际上,BackgroundWorker
类已经为您解决了使用 BeginInvoke
/EndInvoke
的所有问题 ,即使您认为它是“.NET 魔术代码”(对我来说,它只是一个直观且有用的工具)。
关于c# - 在 C# 中做一些工作时显示进度条?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1952201/