winforms 在C#/.net中更新while循环内的进度条和标签的问题

yjghlzjz  于 2023-03-24  发布在  C#
关注(0)|答案(1)|浏览(182)

我遇到了一个(对我来说)很奇怪的问题。
我有一个C#/.net应用程序,其中我使用秒表来获得事件的准确计时。在计时期间,我想用进度更新标签。我的代码(简化):

private void backgroundWorker3_DoWork(object sender, DoWorkEventArgs e)
    {
            double stepDuration = Convert.ToDouble(textBoxPenetrationTime.Text);
            Stopwatch clock = new Stopwatch();
            long freq = Stopwatch.Frequency;
            double ticksPerStep = (double)freq * stepDuration;
            long preTick, postTick;
            clock.Restart();
            preTick = clock.ElapsedTicks;
            while (clock.ElapsedTicks < ticksPerStep)
            {
                labelPercent.BeginInvoke((Action)delegate () { labelPercent.Text = Convert.ToInt32(clock.ElapsedTicks) / 1000000 * 100 / ((Convert.ToInt32(ticksPerStep) / 1000000)) + "%"; });
                if (backgroundWorker3.CancellationPending == true)
                {
                    return;
                }
            }
                postTick = clock.ElapsedTicks;
    }

当我运行start backgroundworker时,UI的其余部分(在另一个backgroundworker中处理的数据绘图和标签冻结,以及整个表单)挂起。
我尝试过的:
更新另一个backgroundworker中的标签并在while循环中调用该backgroundworker(结果相同)。在更新标签后尝试调用Application.DoEvents()(没有区别)
有一件事是可行的,这就是为什么我把这个问题描述为奇怪的原因,那就是如果我调用

Console.WriteLine(clock.ElapsedTicks);

在while循环的开始,一切都按照预期顺利运行。2我只是偶然发现的。
所以我的问题有两部分:
1.为什么UI在写入控制台时运行流畅,而在其他情况下则不然?
1.如何在不写入控制台的情况下解决此问题?

11dmarpk

11dmarpk1#

你需要减少在UI线程上安排工作的次数。每次调用Invoke时,你都会请求更新UI。如果你有一个紧密的循环,你可以轻松地调用Invoke每秒超过60次。除非你是一个游戏玩家和/或LTT的Linus,否则你不会看到/注意到你的标签的所有更新,即使它在每次调用时输出不同的值。
当你添加一个对Console.WriteLine的调用时,你基本上减少了UI线程上的负载,足以再次响应。这是因为Console.WriteLine是一个在CPU周期/ IO负载方面相对昂贵的调用。这是以你的后台任务现在需要更长时间才能完成为代价的。
我建议作以下修改:

ProgressChanged事件的实现

backgroundWorker3.ProgressChanged += (sender, progress) => {
   labelPercent.Text = progress.ProgressPercentage.ToString("#0 \\%"); 
};

下面是您的DoWork在跟踪最后一个百分比时的样子,以便您可以在有理由这样做时调用ReportProgess。

double stepDuration = Convert.ToDouble(10);
Stopwatch clock = new Stopwatch();
long freq = Stopwatch.Frequency;
double ticksPerStep = (double)freq * stepDuration;      
long preTick, postTick;
clock.Restart();
preTick = clock.ElapsedTicks;
var lastPercentage = Convert.ToInt32(clock.ElapsedTicks) / 1000000 * 100 / ((Convert.ToInt32(ticksPerStep) / 1000000));
bgw.ReportProgress(lastPercentage);
while (clock.ElapsedTicks < ticksPerStep)
{
    var nextPercentage = Convert.ToInt32(clock.ElapsedTicks) / 1000000 * 100 / ((Convert.ToInt32(ticksPerStep) / 1000000));
    if (nextPercentage != lastPercentage) {
        bgw.ReportProgress(nextPercentage);
        lastPercentage = nextPercentage;
    }
    // do work
}
bgw.ReportProgress(100);

如果你使用Timer每秒更新标签25次,你的DoWork看起来会是这样的。这是以额外的线程和更多的UI线程调用为代价的,但是你的工人循环没有进度问题。

double stepDuration = Convert.ToDouble(10);
Stopwatch clock = new Stopwatch();
long freq = Stopwatch.Frequency;
double ticksPerStep = (double)freq * stepDuration;
long preTick, postTick;

using(var timer = new System.Threading.Timer(
     (e) => {
        bgw.ReportProgress(Convert.ToInt32(clock.ElapsedTicks) / 1000000 * 100 / ((Convert.ToInt32(ticksPerStep) / 1000000)));
     }, null, 40, 40))
{
    clock.Restart();
    preTick = clock.ElapsedTicks;
    while (clock.ElapsedTicks < ticksPerStep)
    {
       // do work
    }
}
bgw.ReportProgress(100);

下面是一个Gif的计时器解决方案:

相关问题