在BackgroundWorker中线程休眠

4
我写了一个简单的应用程序,使用BackgroundWorker将10万行“Hello World”添加到列表中。
以下是我的BackgroundWorker在单独的线程中执行的工作代码:
    private void BgWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        int min = 0;

        foreach (var hw in hwList)
        {
            //new ManualResetEvent(false).WaitOne(1);
            Thread.Sleep(1);
            int progress = Convert.ToInt32((Double)min / hwList.Count * 100);
            min++;
            bgWorker.ReportProgress(progress);
        }
    }

    // Updating the progress
    private void BgWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
    {
        ProgressBar.Value = progressChangedEventArgs.ProgressPercentage;
    }

除了如果我去掉Thread.Sleep(1)后,BackgroundWorker就不再报告进度之外,一切都正常工作。(我想它需要一些时间)。暂停线程1毫秒实际上使BackgroundWorker报告进度,但速度非常慢。

我的问题是,有没有办法摆脱线程睡眠,同时又能让BackgroundWorker正确地报告进度?

从我的理解来看,挂起BackgroundWorker是不可避免的,因为线程需要一些时间来执行任务,但我想知道是否有一个解决方法。


2
"BackgroundWorker不再报告进度了",你能详细说明一下吗?我认为它仍然会报告进度,只是你没有正确地消耗它。 - Darek
1
那么这就是一个完全不同的问题了。也许它只是发生得太快了,以至于看不到?它最终会变成满的吗?但我们需要看代码才能知道那里发生了什么。 - bkribbs
@bkribbs 它也不会填满。只有当我使用Thread.Sleep(1)暂停线程时,它才会正常填充。 - deefrson
@ Darek,不,这是纯C# /典型的BackgroundWorker代码。我更新了我的代码。确实,我想以可视化的方式观察它,而不仅仅是每秒一次,而是电脑可以处理的每个小时间量。不幸的是,1毫秒是我可以设置的最低值。也许我应该采用不同的方法? - deefrson
2
@deefrson 因为进度是一个整数,所以在一个 2,000,000 循环的第一次和第 10,000 次迭代之间不会有任何区别。两者都是 0。 - Robert McKee
显示剩余8条评论
3个回答

2

我曾经遇到一个问题,就是我过于频繁地汇报进度。此外,没有必要这么多次地汇报相同的进度,这浪费了CPU周期。

 private void BgWorkerOnDoWork(object sender, DoWorkEventArgs doWorkEventArgs)
    {
        int min = 0;
        int oldProgress = 0;
        foreach (var hw in hwList)
        {
            // new ManualResetEvent(false).WaitOne(1);
            // Thread.Sleep(1);
            int progress = Convert.ToInt32((Double)min / hwList.Count * 100);
            min++;

            // Only report progress when it changes
            if(progress != oldProgress){
                 bgWorker.ReportProgress(progress);
                 oldProgress = progress;
            }
        }
    }

    // Updating the progress
    private void BgWorkerOnProgressChanged(object sender, ProgressChangedEventArgs progressChangedEventArgs)
    {
        ProgressBar.Value = progressChangedEventArgs.ProgressPercentage;
    }

好的,我现在明白了,由于进度是一个整数,我多次报告一些值,所以你的代码应该修复这个问题。实际上,CPU周期正在被浪费。我会尝试一些你的代码,如果它能够工作,我会将你的答案标记为已接受。 - deefrson
@deefrson和Marko...不是100%确定,但当您从后台线程更新UI时,必须在Dispatcher线程上执行。BackgroundWorker是否有助于实现这一点?否则,您将会收到跨线程异常。 - Darek
@Darek 在这种情况下,你实际上不需要使用 Dispatcher,因为 BackgroundWorker 与主线程“同步”。无论如何,我已经阅读了一些关于 .NET 4.0 中带有的惊人 TPL(任务并行库)的非常好的东西,它可以简化许多事情。因此,这可能是 BackgroundWorker 的替代方案。 - deefrson
或者,您可以根据时间更新,或者基于“loopIndex%10000 == 0”进行节流。 (+1) - usr
我建议不要使用魔法数字,如果列表的数量发生变化,则需要调整/维护节流数。保持简单,仅在进度发生更改时报告进度,无需进行节流或使用时间。高效和弹性的代码无需添加额外的复杂性。 - Marko
@Marko 确实,解决方案非常明显,再次感谢您。 - deefrson

0

我开发了一个名为SxProgress的类,其接口非常简单,您可以按以下方式使用它,而不是使用BackgroundWorker:

 int DesiredLinesCount = 100000 ;
 List<string> Lines=new List<string>() ;
 object[] UserObjects = new object[] { Lines } ; 
 SxProgress.Execute("Building lines",DesiredLinesCount,true,false,
                    BuildLines_ExecInThread,UserObjects)) ;

 private bool BuildLines_ExecInThread(int ItemIndex,object[] UserObjects)
{
  // some sleep to slow down the process (demonstration purpose only)
  // if (ItemIndex % 10 ==0) System.Threading.Thread.Sleep(1) ; 
  List<string> Lines= (List<String>)UserObjects[0] ;
  Lines.Add("Hello world") ; 
  return true ; 
}

SxProgress 代码在此链接的最后一条消息中

还要注意,该类为并行处理提供了相同的易用接口,可以创建与计算机核心数量相同的线程,并将项目透明地分派到不同的线程中。


0
这个能用在WPF上吗? 不幸的是不能:该类包含一个带有进度条、标签和停止按钮的表单(winforms)。

可能的解决方案我从未尝试过。

可以通过在WPF项目中添加“添加引用”对话框表单(在“.NET”选项卡中)中的2个引用来实现,即“System.Windows.forms”和“WindowsFormsIntegration”。

然后,在源代码中添加“using System.Windows.Forms;”
和“using System.Windows.Forms.Integration;”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接