在我看来,您至少在一个错误的假设上进行操作。
1. 无需引发 ProgressChanged 事件即可拥有响应式 UI
在您的问题中,您这样说:
BackgroundWorker 不是答案,因为我可能没有收到进度通知,这意味着不会调用 ProgressChanged,因为 DoWork 是对外部函数的一次调用。. .
实际上,你是否调用ProgressChanged
事件并不重要。该事件的全部目的是将控制权暂时转移回 GUI 线程,以进行更新,以某种方式反映BackgroundWorker
. 如果您只是显示一个选框进度条,那么引发ProgressChanged
事件实际上是毫无意义的。只要显示进度条,它就会继续旋转,因为它是在与 GUI 不同的BackgroundWorker
线程上工作的。
(附带说明,DoWork
是一个事件,这意味着它不仅仅是“对外部函数的一次调用”;您可以添加任意数量的处理程序;并且每个处理程序都可以包含尽可能多的函数调用喜欢。)
2. 无需调用 Application.DoEvents 即可拥有响应式 UI
对我来说,听起来您认为GUI 更新的唯一方法是调用Application.DoEvents
:
我需要继续调用 Application.DoEvents(); 让进度条保持旋转。
这在多线程场景中是不正确的;如果您使用 a BackgroundWorker
,则 GUI 将继续响应(在其自己的线程上),同时BackgroundWorker
执行附加到其DoWork
事件的任何操作。下面是一个简单的例子,说明这如何为您工作。
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.不能在同一个线程上同时运行两个方法
你这样说:
我只需要调用 Application.DoEvents() 以便 Marque 进度条可以工作,而辅助函数在主线程中工作。. .
你所要求的根本不是真实的。Windows 窗体应用程序的“主”线程是 GUI 线程,如果它忙于您的长时间运行的方法,则它不提供视觉更新。如果您不相信,我怀疑您误解了它的作用:它在单独的线程上BeginInvoke
启动了一个委托。实际上,您在问题中包含的示例代码在and之间调用是多余的;您实际上是从 GUI 线程重复调用,无论如何都会更新。(如果您发现其他情况,我怀疑是因为您立即调用,这会阻塞当前线程,直到方法完成。)Application.DoEvents
exec.BeginInvoke
exec.EndInvoke
Application.DoEvents
exec.EndInvoke
所以是的,您正在寻找的答案是使用BackgroundWorker
.
您可以使用BeginInvoke
, 但不是EndInvoke
从 GUI 线程调用(如果方法未完成,它将阻止它),将AsyncCallback
参数传递给您的BeginInvoke
调用(而不仅仅是传递null
),并在回调中关闭进度表单。但是请注意,如果您这样做,您将不得不调用从 GUI 线程关闭进度表单的方法,否则您将尝试从一个非 GUI 线程。但实际上,使用BeginInvoke
/的所有陷阱EndInvoke
都已经在课程中为您解决了BackgroundWorker
,即使您认为它是“.NET 魔术代码”(对我来说,它只是一个直观且有用的工具)。