【问题标题】:How to wait for background worker to finish processing?如何等待后台工作人员完成处理?
【发布时间】:2013-09-13 01:40:22
【问题描述】:

我有 3 个后台工作人员,每个工作人员处理一个 24 位位图图像(Y、Cb、Cr)的通道。每个 8 位图像的处理需要几秒钟的时间,并且它们可能不会同时完成。

我想在完成后将通道合并回一张图像。单击按钮时,每个backgroundWorkerN.RunWorkerAsync() 都会启动,当它们完成时,我将标志设置为true。我尝试使用 while 循环 while (!y && !cb && !cr) { } 不断检查标志,直到它们为真,然后退出循环并继续处理下面的代码,该代码是将通道重新合并在一起的代码。但是,当我运行它时,这个过程永远不会结束。

   private void button1_Click(object sender, EventArgs e)
   {
        backgroundWorker1.RunWorkerAsync();
        backgroundWorker2.RunWorkerAsync();
        backgroundWorker3.RunWorkerAsync();

        while (!y && !cb && !cr) { }

        //Merge Code
   }

【问题讨论】:

  • 考虑将 while (!y && !cb && !cr) { } 移动到另一个线程中,您将在其中将所有通道的值合并到单个对象中。然后将最终对象传输到您的类中。
  • 这会导致死锁,当您的代码卡在该循环中时,BGW 无法报告进度或完成。永远不要阻塞 UI 线程。

标签: c# winforms backgroundworker parallel-processing


【解决方案1】:

基于 Renuiz 的回答,我会这样做:

private object lockObj;

private void backgroundWorkerN_RunWorkerCompleted(
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    lock (lockObj)
    {
        y = true;
        if (cb && cr) // if cb and cr flags are true - 
                      // other backgroundWorkers finished work
        {
            someMethodToDoOtherStuff();
        }
    }
}

【讨论】:

    【解决方案2】:

    也许您可以在后台工作人员完成事件处理程序中设置和检查标志。例如:

    private void backgroundWorkerN_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        y = true;
        if(cb && cr)//if cb and cr flags are true - other backgroundWorkers finished work
           someMethodToDoOtherStuff();
    }
    

    【讨论】:

    • 如果他们碰巧同时完成怎么办? someMethodToDoOtherStuff() 有可能被调用 3 次。
    • 您可以检查在 someMethodToDoOtherStuff 中设置的另一个标志(processingFlag)或在您的答案中使用锁定。
    【解决方案3】:

    我会使用三个线程而不是后台工作人员。

    using System.Threading;
    
    class MyConversionClass
    {
        public YCBCR Input;
        public RGB Output
    
        private Thread Thread1;
        private Thread Thread2;
        private Thread Thread3;
    
        private int pCompletionCount;
    
        public MyConversionClass(YCBCR myInput, RGB myOutput)
        {
            this.Input = myInput;
            this.Output = myOutput;
    
            this.Thread1 = new Thread(this.ComputeY);
            this.Thread2 = new Thread(this.ComputeCB);
            this.Thread3 = new Thread(this.ComputeCR);
        }
    
        public void Start()
        {
            this.Thread1.Start();
            this.Thread2.Start();
            this.Thread3.Start();
        }
    
        public void WaitCompletion()
        {
            this.Thread1.Join();
            this.Thread2.Join();
            this.Thread3.Join();
        }
    
        // Call this method in background worker 1
        private void ComputeY()
        {
            // for each pixel do My stuff
            ...
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        // Call this method in background worker 2
        private void ComputeCB()
        {
            // for each pixel do My stuff
            ...
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        // Call this method in background worker 3
        private void ComputeCR()
        {
            // for each pixel do My stuff
            ...
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        private void MergeTogether()
        {
            // We merge the three channels together
            ...
        }
    }
    

    现在在您的代码中,您只需执行以下操作:

    private void button1_Click(object sender, EventArgs e)
    {
        MyConversionClass conversion = new MyConversionClass(myinput, myoutput);
        conversion.Start();
        conversion.WaitCompletion();
    
        ... your other stuff
    }
    

    但是,这会暂停您的 GUI,直到所有操作完成。 我会使用 SynchronizationContext 来通知 GUI 操作已完成。

    此版本使用 SynchronizationContext 来同步 GUI 线程而无需等待。 这将使 GUI 保持响应并在其他线程中执行整个转换操作。

    using System.Threading;
    
    class MyConversionClass
    {
        public YCBCR Input;
        public RGB Output
    
        private EventHandler Completed;
    
        private Thread Thread1;
        private Thread Thread2;
        private Thread Thread3;
        private SynchronizationContext SyncContext;
    
        private volatile int pCompletionCount;
    
        public MyConversionClass()
        {
            this.Thread1 = new Thread(this.ComputeY);
            this.Thread2 = new Thread(this.ComputeCB);
            this.Thread3 = new Thread(this.ComputeCR);
        }
    
        public void Start(YCBCR myInput, RGB myOutput, SynchronizationContext syncContext, EventHandler completed)
        {
            this.SyncContext = syncContext;
            this.Completed = completed;
            this.Input = myInput;
            this.Output = myOutput;
    
            this.Thread1.Start();
            this.Thread2.Start();
            this.Thread3.Start();
        }
    
        // Call this method in background worker 1
        private void ComputeY()
        {
            ... // for each pixel do My stuff
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        // Call this method in background worker 2
        private void ComputeCB()
        {
            ... // for each pixel do My stuff
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        // Call this method in background worker 3
        private void ComputeCR()
        {
            ... // for each pixel do My stuff
            if (Interlocked.Increment(ref this.CompletionCount) == 3)
                this.MergeTogether();
        }
    
        private void MergeTogether()
        {
            ... // We merge the three channels together
    
            // We finish everything, we can notify the application that everything is completed.
            this.syncContext.Post(RaiseCompleted, this);
        }
    
        private static void RaiseCompleted(object state)
        {
            (state as MyConversionClass).OnCompleted(EventArgs.Empty);
        }
    
        // This function is called in GUI thread when everything completes.
        protected virtual void OnCompleted(EventArgs e)
        {
            EventHandler completed = this.Completed;
            this.Completed = null;
            if (completed != null)
                completed(this, e);
        }
    }
    

    现在,在您的代码中...

    private void button1_Click(object sender, EventArgs e)
    {
        button1.Enabled = false;
    
        MyConversionClass conversion = new MyConversionClass();
        conversion.Start(myinput, myoutput, SynchronizationContext.Current, this.conversion_Completed);
    }
    
    private void conversion_Completed(object sender, EventArgs e)
    {
        var output = (sender as MyConversionClass).Output;
        ... your other stuff that uses output
    
        button1.Enabled = true;
    }
    

    这两种方法的好处是它们与 GUI 无关,您可以将它们放在一个库中,并使您宝贵的多线程转换代码完全独立于您使用的 GUI,即 WPF、Web 或 Windows 窗体.

    【讨论】:

      【解决方案4】:

      您可以将WaitHandle.WaitAllEventWaitHandle 结合使用来实现您的需要。这里附上了一个代码示例,它完成了我提到的操作。随附的代码只是解决方案外观的概述。您必须添加适当的异常处理和防御方法以使此代码更稳定。

      using System;
      using System.ComponentModel;
      using System.Threading;
      
      namespace ConsoleApplication7
      {
          class Program
          {
              static void Main(string[] args)
              {
                  BWorkerSyncExample sample = new BWorkerSyncExample();
                  sample.M();
              }
          }
          class BWorkerSyncExample
          {
              BackgroundWorker worker1, worker2, worker3;
              EventWaitHandle[] waithandles;
      
              public void M()
              {
                  Console.WriteLine("Starting background worker threads");
                  waithandles = new EventWaitHandle[3];
      
                  waithandles[0] = new EventWaitHandle(false, EventResetMode.ManualReset);
                  waithandles[1] = new EventWaitHandle(false, EventResetMode.ManualReset);
                  waithandles[2] = new EventWaitHandle(false, EventResetMode.ManualReset);
      
                  StartBWorkerOne();
                  StartBWorkerTwo();
                  StartBWorkerThree();
      
                  //Wait until all background worker complete or timeout elapse
                  Console.WriteLine("Waiting for workers to complete...");
                  WaitHandle.WaitAll(waithandles, 10000);
                  Console.WriteLine("All workers finished their activities");
                  Console.ReadLine();
              }
      
              void StartBWorkerThree()
              {
                  if (worker3 == null)
                  {
                      worker3 = new BackgroundWorker();
                      worker3.DoWork += (sender, args) =>
                                          {
      
                                              M3();
                                              Console.WriteLine("I am done- Worker Three");
                                          };
                      worker3.RunWorkerCompleted += (sender, args) =>
                                          {
                                              waithandles[2].Set();
                                          };
      
                  }
                  if (!worker3.IsBusy)
                      worker3.RunWorkerAsync();
              }
      
              void StartBWorkerTwo()
              {
                  if (worker2 == null)
                  {
                      worker2 = new BackgroundWorker();
                      worker2.DoWork += (sender, args) =>
                                             {
      
                                                 M2();
                                                 Console.WriteLine("I am done- Worker Two");
                                             };
                      worker2.RunWorkerCompleted += (sender, args) =>
                                             {
                                                 waithandles[1].Set();
                                             };
      
                  }
                  if (!worker2.IsBusy)
                      worker2.RunWorkerAsync();
              }
      
              void StartBWorkerOne()
              {
                  if (worker1 == null)
                  {
                      worker1 = new BackgroundWorker();
                      worker1.DoWork += (sender, args) =>
                                             {
      
                                                 M1();
                                                 Console.WriteLine("I am done- Worker One");
                                             };
                      worker1.RunWorkerCompleted += (sender, args) =>
                                             {
                                                 waithandles[0].Set();
                                             };
      
                  }
                  if (!worker1.IsBusy)
                      worker1.RunWorkerAsync();
              }
              void M1()
              {
                 //do all your image processing here.
              //simulate some intensive activity.
              Thread.Sleep(3000);
              }
              void M2()
              {
                //do all your image processing here.
              //simulate some intensive activity.
              Thread.Sleep(1000);
              }
              void M3()
              {
               //do all your image processing here.
              //simulate some intensive activity.
              Thread.Sleep(4000);
              }
      
          }
      }
      

      【讨论】:

        【解决方案5】:

        考虑使用 AutoResetEvents:

        private void button1_Click(object sender, EventArgs e)
            {
                var e1 = new System.Threading.AutoResetEvent(false);
                var e2 = new System.Threading.AutoResetEvent(false);
                var e3 = new System.Threading.AutoResetEvent(false);
        
                backgroundWorker1.RunWorkerAsync(e1);
                backgroundWorker2.RunWorkerAsync(e2);
                backgroundWorker3.RunWorkerAsync(e3);
        
        
                // Keep the UI Responsive
                ThreadPool.QueueUserWorkItem(x =>
                {
                    // Wait for the background workers
                    e1.WaitOne();
                    e2.WaitOne();
                    e3.WaitOne();
                    MethodThatNotifiesIamFinished();
                });
        
                //Merge Code
            }
        
        
            void BackgroundWorkerMethod(object obj)
            {
                var evt = obj as AutoResetEvent;
                //Do calculations
                etv.Set();
            }
        

        这样您就不会在某些循环中浪费 cpu 时间,并且使用单独的线程等待保持 UI 响应。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-07-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-09
          相关资源
          最近更新 更多