Как дождаться завершения обработки фоновым рабочим?

#c# #winforms #backgroundworker #parallel-processing

#c# #winforms #фоновый рабочий #параллельная обработка

Вопрос:

У меня есть 3 фоновых рабочих, каждый из которых обрабатывает канал 24-битного растрового изображения (Y, Cb, Cr). Обработка для каждого 8-битного изображения занимает несколько секунд, и они могут не завершиться одновременно.

Я хочу объединить каналы обратно в одно изображение, когда закончу. При нажатии кнопки запускается каждый из backgroundWorkerN.RunWorkerAsync() них, и когда они завершаются, я устанавливаю флаг для true. Я попытался использовать цикл while while (!y amp;amp; !cb amp;amp; !cr) { } , чтобы постоянно проверять флаги, пока они не станут истинными, затем выйти из цикла и продолжить обработку кода, приведенного ниже, который представляет собой код для объединения каналов вместе. Но вместо этого процесс никогда не заканчивается, когда я его запускаю.

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

        while (!y amp;amp; !cb amp;amp; !cr) { }

        //Merge Code
   }
 

Комментарии:

1. Подумайте о том, чтобы переместить while (!y amp;amp; !cb amp;amp; !cr) { } в еще один поток, где вы объедините значения из всех каналов в один объект. Затем передайте этот конечный объект в свой класс.

2. Это приводит к взаимоблокировке, BGWS не может сообщить о прогрессе или завершении, пока ваш код застрял в этом цикле. Никогда не блокируйте поток пользовательского интерфейса.

Ответ №1:

Основываясь на ответе Renuiz, я бы сделал это так:

 private object lockObj;

private void backgroundWorkerN_RunWorkerCompleted(
    object sender, 
    RunWorkerCompletedEventArgs e)
{
    lock (lockObj)
    {
        y = true;
        if (cb amp;amp; 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 amp;amp; cr)//if cb and cr flags are true - other backgroundWorkers finished work
       someMethodToDoOtherStuff();
}
 

Комментарии:

1. Что, если все они завершатся одновременно? существует вероятность того, что someMethodToDoOtherStuff() может быть вызван 3 раза.

2. вы можете проверить другой флаг (processingFlag), который установлен в someMethodToDoOtherStuff, или использовать блокировку, как в вашем ответе.

Ответ №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
}
 

Однако это приостановит ваш графический интерфейс до завершения всех операций.
Вместо этого я бы использовал SynchronizationContext, чтобы уведомить графический интерфейс о завершении операции.

Эта версия использует SynchronizationContext для синхронизации потока 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;
}
 

Плюсы обоих методов в том, что они не зависят от графического интерфейса пользователя, вы можете поместить их в библиотеку и сохранить свой драгоценный многопоточный код преобразования полностью независимым от используемого вами графического интерфейса, то есть WPF, Web или Windows Forms.

Ответ №4:

Вы можете использовать WaitHandle.WaitAll в сочетании с EventWaitHandle для достижения того, что вам нужно. Здесь прилагается пример кода, который выполняет то, что я упомянул. Прилагаемый код — это всего лишь набросок того, как будет выглядеть решение. Вы должны добавить правильную обработку исключений и защитный подход, чтобы сделать этот код более стабильным.

 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();
    }
 

Таким образом, вы не тратите время процессора в некоторых циклах, а использование отдельного потока для ожидания позволяет адаптировать пользовательский интерфейс.