#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();
}
Таким образом, вы не тратите время процессора в некоторых циклах, а использование отдельного потока для ожидания позволяет адаптировать пользовательский интерфейс.