【问题标题】:Parallel.Foreach + BackgroundWorker.ReportProgressParallel.Foreach + BackgroundWorker.ReportProgress
【发布时间】:2017-07-06 20:53:51
【问题描述】:

如果有任何已知原因我不应该这样做,我正在寻找反馈:将 ReportProgress 放在并行 foreach 语句中。是否有任何类似的方法可以实现相同的目标,但被认为更安全或更好?

代码示例:

        int iterations = 0;
        int count = items.Count;
        Parallel.ForEach(items, new ParallelOptions() { MaxDegreeOfParallelism = 5 }, item =>
        {
            iterations++;
            int percentComplete = (iterations/count) * 100;
            backgroundWorker.ReportProgress(percentComplete);
        };

附加信息:

我有一个需要执行管理项目的特定任务的 Windows 窗体程序。使用多线程使其速度更快。但是,它需要向表单上的进度条报告已完成的整体进度,因此最终用户不会怀疑程序是否冻结。在某些情况下,整个工作可能需要几个小时。因此,必须要有进度条。

【问题讨论】:

  • 只需将ProgressBar.Style() Property 设置为“Marquee”。
  • @Idle_Mind 我不认为“Marquee”将成为这种情况的一个选项:重要的是要了解工作完成了多远才能了解剩余时间。
  • 您的上述代码如何报告任何有意义的进度?
  • @Idle_Mind 是伪代码,您提到的问题(现在已通过编辑修复)并没有真正影响问题的重点。但为避免将来对实际问题造成任何混淆或干扰,已编辑...
  • 您仍然没有报告任何有意义的进展。代码只是简单地迭代一个不知道 BackgroundWorkers 中正在完成的实际工作的值。您可以在表单上使用标准计时器并在 Tick() 事件中调用 ReportProgress()。为什么需要额外的线程来完成这项任务?

标签: c# multithreading winforms backgroundworker parallel.foreach


【解决方案1】:

我认为这是不明智的,甚至可能不允许让多个线程与同一个BackGroundWorker 通信。

如果您只有有限数量的线程,例如您的示例中的 5 个,请考虑让每个线程使用自己的 BackgroundWorker 来报告自己的进度。

所有进度报告的接收者应在收到报告后确定实际进度并将其报告给感兴趣的人。

class MyLongProgress : IDisposable
{
     public MyLongProgress()
     {
         for (int i=0; i<maxNrOfParallelProcesses; ++i)
         {
             var createdBackGroundWorker = this.CreateBackGroundWorker();
             createdBackGroundWorker.ReportProgress += this.OnProgressReport;
             backGroundWorkers.Add(createdBackGroundWorker);
         }
     }
     // TODO: implement Disposable pattern that Disposes the background workers
     private readonly List<BackGroundWorker> backGroundWorkers = new List<BackGroundWorker>();

     public void StartLongProcess()
     {
         // start your long progress, which involves starting the backGroundWorkers
         // These backGroundWorkers will report progress via OnProgressReport
     }

     private void OnProgressReport(object sender, ...)
     {
         var myBackGroundWorker = (BackGroundWorker)sender;
         // calculate the actual progress using earlier received progress reports
         MyProgressReport report = ...
         OnReportProgress(report);
     }

     public event EventHandler<MyprogressReport> ReportProgrogress;

     protected virtual void OnReportProgress(MyProgressReport report)
     {
         this.ReportProgress?.Invoke(this, report);
     }
}

或者:仅在 StartLongProcess 期间创建和处置 BackGroundWorker。在这种情况下,您不需要 IDisposable。

【讨论】:

  • "...甚至可能不允许让多个线程与同一个 BackGroundWorker 进行通信。"我的示例代码确实有效,并且这种技术没有已知问题。我的进度条会按照我的预期更新。根据一位同事的说法,你做了同样的事情,这就是我问这个问题以获得澄清的原因。您能否详细说明您建议的方法如何或为什么更好?
【解决方案2】:

我认为 Harald 的回答很成功。 在拥有一名后台工作人员的情况下,我使用了this link 中显示的示例。

您可以很好地了解它如何与以下 sn-p 一起工作(从上面的链接复制)。我认为您可以使用它作为基础来允许 DoWorkEventHandler 代表的集合并为每个代表创建一个 BackgroundWorker

      private void button4_Click(object sender, EventArgs e)
      {
         // Create dialog.
         ProgressWithCancel dlgProgress = new ProgressWithCancel( "Testing progress", LongOperation);

         // Show dialog with Synchronous/blocking call.
         // LongOperation() is called by dialog.
         dlgProgress.ShowDialog(); // Synchronous/blocking call.
      }

      private void LongOperation(object sender, DoWorkEventArgs e)
      {
         BackgroundWorker worker = sender as BackgroundWorker;

         int max = 20;
         for (int i = 0; i < max; i++)
         {
            if (worker.CancellationPending) // See if cacel button was pressed.
            {
               System.Threading.Thread.Sleep(2000); // Similate time for clean-up.
               break;
            }

            int percent = i * 100 / max;

            string userState = percent.ToString(); // render a string to display on the progress dialog.

            // Append to string just to show multi-line user-status info.
            if (percent >= 45 && percent <= 55) { userState += "Half way"; }
            if (percent >= 85) { userState += "Almost done"; }

            worker.ReportProgress(percent, userState); // Report percent and user-status info to dialog.

            System.Threading.Thread.Sleep(800); // Simulate time-consuming operation
         }
      }


// Implementation (in ProgressWithCancel.cs)

namespace ProgressWithCancel
{
   public partial class ProgressWithCancel : Form
   {
      public ProgressWithCancel(string whyWeAreWaiting, DoWorkEventHandler work)
      {
         InitializeComponent();
         this.Text = whyWeAreWaiting; // Show in title bar
         backgroundWorker1.DoWork += work; // Event handler to be called in context of new thread.
      }

      private void btnCancel_Click(object sender, EventArgs e)
      {
         label1.Text = "Cancel pending";
         backgroundWorker1.CancelAsync(); // Tell worker to abort.
         btnCancel.Enabled = false;
      }

      private void Progress_Load(object sender, EventArgs e)
      {
         backgroundWorker1.RunWorkerAsync();
      }

      private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
      {
         progressBar1.Value = e.ProgressPercentage;
         label1.Text = e.UserState as string;
      }

      private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         Close();
      }

   }
}

【讨论】:

  • 我看不出这是如何利用任何类型的多线程同时运行多个任务,这是我的问题的一个重要部分
  • 那个例子没有。它只使用一个。我只是将它作为示例发布,并表示您可以将其用作起点,但将其更新为与多个 BackgroundWorker 一起使用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 2012-04-12
  • 2011-06-27
相关资源
最近更新 更多