c# - 在 C# 中做一些工作时显示进度条?

标签 c# multithreading .net-3.5 begininvoke

我想在做一些工作时显示进度条,但这会挂起 UI,并且进度条不会更新。

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

using(ProgressForm p = new ProgressForm(this))
{
//Do Some Work
}

现在有很多方法可以解决这个问题,比如使用BeginInvoke,等待任务完成再调用EndInvoke。或者使用 BackgroundWorkerThreads

我在使用 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.BeginInvokeexec.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/

相关文章:

Java 并发问题 - Locks 和 Synchronize 方法

c# - IList.Cast<typeof(T)>() 返回错误,语法看起来没问题

C# 自定义 Observable 集合——我应该使用组合还是继承?

.net - 如何在 WPF 中的控件顶部显示进度条

c# - 如何将图像设置为缩放至其表格单元格的 100%? (C#/WinForms)

c# - lambda 是否在每次调用时都创建一个新实例?

c# - 将字节数组转换为字节字符串

c# - 如何将数据绑定(bind)到 HTML 选择框

java - 使用 sleep() 和 interrupt() 重用线程

.net - 如何在没有缓冲区的情况下同时将 .NET 流写入另外两个流?