【问题标题】:Progressbar not showing the correct values while Multi-Threading多线程时进度条未显示正确的值
【发布时间】:2013-07-06 19:35:38
【问题描述】:

在我的 MainForm 上,我启动了一个线程来执行一些与 SQL 相关的工作。同时,我希望显示一个带有进度条的新表单,以确认程序仍在响应。我遇到的问题是进度条没有显示正确的值。我希望进度条一遍又一遍地从最小值变为最大值,只是为了确保用户不会卡住程序。然而,进度条并没有按照我想要的方式出现,因为它在达到最大值的 2/3 时会重置。所以进度条显示的值从最小值到最大值的 2/3。下面是进度条重置为Value = 0;时的图片

MainForm 包含以下代码:

List<Table> ContentList = new List<Table>();
using (ConnectingForm CF = new ConnectingForm())
{
    CF.StartPosition = FormStartPosition.Manual;
    CF.Show(this);
    Thread thread = new Thread(() => { ContentList = DBL.LoadSQLData(); });
    thread.Start();
    DBL.SQLdone = false;
    while (!DBL.SQLdone);
    this.Focus();
    CF.Hide();
}

ConnectingForm 的代码如下:

public ConnectingForm()
{
    InitializeComponent();
    progressBar1.Value = 0;
    progressBar1.Minimum = 0;
    progressBar1.Maximum = 50;
    progressBar1.Step = 1;
    timer1.Interval = 25;
    timer1.Enabled = true;
}

private void timer1_Tick(object sender, EventArgs e)
{
    if (progressBar1.Value < progressBar1.Maximum)
        progressBar1.PerformStep();
    else
        progressBar1.Value = 0;
}

我还尝试在 while 循环中设置进度条的值。但是问题仍然存在:UI 没有显示进度条值增加的最后 1/3 的动画。目前代码如下所示:

主窗体:

...
Thread thread = new Thread(() => { ContentList = DBL.LoadSQLData(); });
thread.Start();
DBL.SQLdone = false;
while (!DBL.SQLdone)
{
    if (CF.Value == CF.Maximum)
         CF.Value = 0;
    else
         CF.Value += 1;
    Thread.Sleep(100);
}
...

虽然 ConnectingForm 看起来像这样:

public ConnectingForm()
{
    InitializeComponent();
    progressBar1.Value = 0;
    progressBar1.Minimum = 0;
    progressBar1.Maximum = 20;
    progressBar1.Step = 1;
}
public int Value
{
    set { progressBar1.Value = value; }
    get { return progressBar1.Value; }
}
public int Maximum
{
    get { return progressBar1.Maximum; }
}

仍然给出相同的结果。进度条只显示值 0 到 2/3,然后重置。

我希望有人可以帮助我找出我做错了什么。 提前致谢!

【问题讨论】:

  • 代码看起来不错,可能只是来自 progbar 的延迟更新。

标签: c# .net multithreading winforms progress


【解决方案1】:

我认为ProgressBar 旨在表示进度有点慢,这样快速或快速的进度(在 25 毫秒内改变 1/50)无法正确(及时)更新。而ProgressBar.Value 仍然增加并达到最大值以重新启动循环。当时,ProgressBar 视觉指示器刚刚达到最大值的 2/3(与实际 Value 相比有所延迟),但不知何故,它会在设置值 0 并且 @ 987654327@ 剩余的视觉指示器未更新/绘制/渲染。如果您将计时器的Interval 增加到1000,您可以看到它在从头开始之前贯穿所有条形。我认为这是设计使然,不能改变它的工作方式。你在这里有一些选择(我能想到):

1. Try creating your own progress indicator and make it update the visual indicator the way you want, however I think it may cause some small issue related to performance.
2. Try cutting off the `non-updated visual part of your ProgressBar` using `Region` property like this:

    //First, try drawing the length of your ProgressBar more because we have to cut off the remaining non-updated visual part of it.
    yourProgressBar.Region = new Region(new Rectangle(Point.Empty, new Size(2*yourProgressBar.Width/3, yourProgressBar.Height)));//placing this in your Form constructor is OK.

也许您必须测试它是否贯穿进度条的所有长度,并相应地更正传入RegionRectangleWidth

更新

事实上ProgressBar支持工作进度指示器(不关心百分比),我想你可能想在不创建任何其他控件或使用第三方控件的情况下使用它。你需要的都在这里:

yourProgressBar.Style = ProgressBarStyle.Marquee;
yourProgressBar.MarqueeAnimationSpeed = 1;//Change the speed of marquee animation
//You don't need any timer to change its Value at all.

【讨论】:

  • 第二种解决方案可能会起作用,但我宁愿不使用“黑客”。我可能会考虑创建自己的对象并处理 Windows 进度条...感谢您花时间解释为什么它不起作用 :)
  • @Robin 如果是这样,你应该去第一个解决方案,我认为表明有一些工作不需要快速进展,你可以通过一些 GDI+ 的基本知识轻松创建自己的指标或者你可能想使用一些第三方控件,DotNetBar 支持一些看起来很棒的指标。
  • 更新:这真是太棒了!这解决了我所有的问题。非常感谢您,先生!
【解决方案2】:

首先要做的事情:通常您不是在计时器中更改进度条的值,而是在流程中的每个工作单元(一个步骤)上更改,因此在您的情况下,进度条应该在 while循环

从您的代码中无法看到计时器何时启动和停止!看起来您将更多步骤添加到进度条作为定义的最大值!您每 25 毫秒更改一次进度条的值,该值与您的 SQL 查询加载/读取/等完全没有关系。

因此您需要更改程序的流程和逻辑才能设置正确的值。如果要从线程更新进度条,还需要注意对 UI 元素的跨线程访问。

【讨论】:

  • 好的,谢谢你的提示。我尝试在 while 循环中更改进度条值。但是在 UI 没有显示 1/3 的动画时仍然会出现问题。
  • 进度条最大值应该是查询结果的计数 - 所有迭代。
  • 嗯,我的目标不是显示完成了多少百分比的进度。我想要做的是确保用户该进程仍在运行并且他不必强行关闭应用程序。有点像过去的旋转沙漏窗。
  • 那么在这种情况下,进度条根本就不是一个合适的指示器。只需在图片框控件中显示动画 gif 或在计时器中创建老式 移动点 指示器 . -> .. -> ... -> . -> 等等使用计数器。通过文字与用户交流:请耐心等待。正在进行中...。将光标设置为wait symbol
【解决方案3】:

这个

Application.DoEvents();
Thread.Sleep(10);

是邪恶的。不要那样做。而且,此外,您不需要它。我认为没有它也能正常工作。

【讨论】:

  • 不是真的,正确使用Application.DoEvents() 就可以了。当然,在部署应用程序之前先对其进行测试以发现任何问题。至少,我不知道有什么其他方法可以替换Application.DoEvents()
  • 但是如果 MainThread 卡在while (!DBL.SQLdone); 线上,UI 将如何工作?
  • @KingKing 你不知道你是否“正确”地使用它。有许多可能的副作用。它通常被用作“穷人的多线程”,但大多数情况下它使事情变得比它所带来的好处更糟。应该完全避免。这不仅是我个人的看法。还有许多其他参考资料。
  • @Robin:你永远不应该阻塞你的主线程。这就是你使用线程的原因,不是吗。
  • 我只是在想我们如何在代码中暂停,直到线程完成工作。 Thread.Join() 过程是否允许 UI 更新?
【解决方案4】:

你可以试试换行吗:

if (progressBar1.Value < progressBar1.Maximum)

用这个:

if (progressBar1.Value <= progressBar1.Maximum)

【讨论】:

  • 这实际上使 Progressbar 一直走到最后,但程序永远不会到达 else 行。所以我可能不得不添加一个全局 bool 或其他东西来确定是否它应该被重置。我会试试这个,然后用结果回复你。干杯!
  • 上述工作是因为程序增加值直到达到最大值,然后程序停止。但是,所有重置 Value 的方法都会导致 UI 跟不上,并且会丢失一些可视化效果。
猜你喜欢
  • 2018-10-27
  • 2021-04-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多