Comment attendre correctement Jusqu'à ce que BackgroundWorker termine?
respecter le code suivant:
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnAsyncOperationCompleted;
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
supposons maintenant que je veuille attendre que bw
finisse de travailler. Quelle est la bonne façon de le faire?
ma solution est celle-ci:
bool finished = false;
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
OnAsyncOperationCompleted(sender, args);
finished = true;
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
int timeout = N;
while (!finished && timeout > 0)
{
Thread.Sleep(1000);
--timeout;
}
if (!finished)
{
throw new TimedoutException("bla bla bla");
}
mais je n'aime pas ça.
j'ai envisagé de remplacer le drapeau finished
par un événement de synchronisation, le mettre dans le handler RunWorkerCompleted
et le bloquer plus tard au lieu de faire le pendant le sommeil de la boucle.
hélas, c'est faux, parce que le code peut fonctionner dans le contexte de synchronisation WPF ou WindowsForm, auquel cas je bloquerais le même thread que le handler RunWorkerCompleted
fonctionne, ce qui est clairement pas très intelligent.
j'aimerais connaître une meilleure solution.
Merci.
EDIT:
P.S.
- le code de l'échantillon est conçu intentionnellement pour clarifier ma question. Je suis parfaitement conscient du rappel de finalisation et pourtant je veux savoir comment attendre jusqu'à la fin. C'est ma question.
- je suis au courant
Thread.Join
,Delegate.BeginInvoke
,ThreadPool.QueueUserWorkItem
, etc... La question porte spécifiquement surBackgroundWorker
.
EDIT 2:
OK, je suppose que ce sera beaucoup plus facile si je expliquer le scénario.
j'ai une méthode d'essai unitaire, qui invoque un code asynchrone, qui à son tour engage finalement un BackgroundWorker
à laquelle je suis capable de passer un handler de finition. Tout le code est à moi, donc je peux changer la mise en œuvre si je le souhaite.
Je ne vais pas, cependant, remplacer le BackgroundWorker
, parce qu'il utilise automatiquement le bon contexte de synchronisation, de sorte que lorsque le code est invoqué sur un thread de L'interface utilisateur, le rappel d'achèvement est invoqué sur le même fil UI, qui est très bon.
quoi qu'il en soit, il est possible que la méthode d'essai unitaire touche la fin avant que le BW termine son travail, ce qui n'est pas bon. Donc je souhaite attendre jusqu'à ce que le BW complète et voudrais savoir le meilleur moyen pour elle.
Il y a plus de pièces, mais le tableau d'ensemble est plus ou moins comme je viens de décrire.
10 réponses
essayez d'utiliser la classe AutoResetEvent comme ceci:
var doneEvent = new AutoResetEvent(false);
var bw = new BackgroundWorker();
bw.DoWork += (sender, e) =>
{
try
{
if (!e.Cancel)
{
// Do work
}
}
finally
{
doneEvent.Set();
}
};
bw.RunWorkerAsync();
doneEvent.WaitOne();
attention: vous devez vous assurer que doneEvent.Set()
est appelé peu importe ce qui se passe. Aussi, vous pourriez vouloir fournir le doneEvent.WaitOne()
avec un argument spécifiant une période de timeout.
Note: ce code est à peu près une copie de Fredrik Kalseth réponse à une question similaire .
pour attendre un fil ouvrier d'arrière-plan (simple ou multiple) faire ce qui suit:
-
créez une liste des travailleurs D'arrière-plan que vous avez créés de façon programmatique:
private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
-
ajouter le travailleur de fond dans la liste:
BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += new DoWorkEventHandler(worker_DoWork); worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged); worker.WorkerReportsProgress = true; m_WorkersWithData.Add(worker); worker.RunWorkerAsync();
-
utilisez la fonction suivante pour attendre tous les travailleurs de la liste:
private void CheckAllThreadsHaveFinishedWorking() { bool hasAllThreadsFinished = false; while (!hasAllThreadsFinished) { hasAllThreadsFinished = (from worker in m_WorkersWithData where worker.IsBusy select worker).ToList().Count == 0; Application.DoEvents(); //This call is very important if you want to have a progress bar and want to update it //from the Progress event of the background worker. Thread.Sleep(1000); //This call waits if the loop continues making sure that the CPU time gets freed before //re-checking. } m_WorkersWithData.Clear(); //After the loop exits clear the list of all background workers to release memory. //On the contrary you can also dispose your background workers. }
BackgroundWorker est un événement d'achèvement. Au lieu d'attendre, appelez votre code restant chemin à partir du gestionnaire d'achèvement.
cette question Est ancienne mais je ne pense pas que l'auteur ait eu la réponse qu'il cherchait.
C'est un peu sale, et c'est vb.NET mais travaille pour moi
Private Sub MultiTaskingForThePoor()
Try
'Start background worker
bgwAsyncTasks.RunWorkerAsync()
'Do some other stuff here
For i as integer = 0 to 100
lblOutput.Text = cstr(i)
Next
'Wait for Background worker
While bgwAsyncTasks.isBusy()
Windows.Forms.Application.DoEvents()
End While
'Voila, we are back in sync
lblOutput.Text = "Success!"
Catch ex As Exception
MsgBox("Oops!" & vbcrlf & ex.Message)
End Try
End Sub
VB.NET
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
vous pouvez l'utiliser pour enchaîner plusieurs événements. (code sudo à suivre)
download_file("filepath")
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
'Waits to install until the download is complete and lets other UI events function install_file("filepath")
While BackgroundWorker1.IsBusy()
Windows.Forms.Application.DoEvents()
End While
'Waits for the install to complete before presenting the message box
msgbox("File Installed")
vérification backgrWorker.IsBusy
dans la boucle avec Application.DoEvents()
n'est pas une bonne façon.
je suis d'accord avec @JohannesH, vous devriez définitivement utiliser AutoResetEvent comme une solution élégante. Mais ne pas l'utiliser dans le fil UI, il va causer le fil principal bloqué; il devrait provenir d'un autre fil ouvrier d'arrière-plan.
AutoResetEvent aevent = new AutoResetEvent(false);
private void button1_Click(object sender, EventArgs e)
{
bws = new BackgroundWorker();
bws.DoWork += new DoWorkEventHandler(bw_work);
bws.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_complete);
bws.RunWorkerAsync();
bwWaiting.DoWork += new DoWorkEventHandler(waiting_work);
bwWaiting.RunWorkerCompleted += new RunWorkerCompletedEventHandler(waiting_complete);
bwWaiting.RunWorkerAsync();
}
void bw_work(object sender, DoWorkEventArgs e)
{
Thread.Sleep(2000);
}
void bw_complete(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("complete " + bwThread.ToString());
aevent.Set();
}
void waiting_work(object sender, DoWorkEventArgs e)
{
aevent.WaitOne();
}
void waiting_complete(object sender, RunWorkerCompletedEventArgs e)
{
Debug.WriteLine("complete waiting thread");
}
Je ne sais pas ce que vous voulez dire par attendre. Voulez-vous dire que vous voulez quelque chose fait (par le BW) après que thats fait vous voulez faire quelque chose d'autre? Utilisez bw.Runworker terminé comme vous le faites (utilisez une fonction séparée pour la lisibilité) et dans cette fonction de rappel vous faire prochain truc. Démarrez une minuterie pour vérifier si le travail ne prend pas trop de temps.
var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
OnAsyncOperationCompleted(sender, args);
});
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);
Timer Clock=new Timer();
Clock.Interval=1000;
Clock.Start();
Clock.Tick+=new EventHandler(Timer_Tick);
public void Timer_Tick(object sender,EventArgs eArgs)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
throw new TimedoutException("bla bla bla");
}
In the OnDoWorkLoadChildren:
if ((worker.CancellationPending == true))
{
e.Cancel = true;
//return or something
}
je cherchais également une solution appropriée. J'ai résolu l'attente avec une serrure exclusive. Le chemin critique dans le code est l'écriture à un conteneur public (ici la console) et l'augmentation ou la diminution des travailleurs. Aucun thread ne doit interférer lors de l'écriture de cette variable, sinon le nombre n'est plus garanti.
public class Program
{
public static int worker = 0;
public static object lockObject = 0;
static void Main(string[] args)
{
BackgroundworkerTest backgroundworkerTest = new BackgroundworkerTest();
backgroundworkerTest.WalkDir("C:\");
while (backgroundworkerTest.Worker > 0)
{
// Exclusive write on console
lock (backgroundworkerTest.ExclusiveLock)
{
Console.CursorTop = 4; Console.CursorLeft = 1;
var consoleOut = string.Format("Worker busy count={0}", backgroundworkerTest.Worker);
Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth-consoleOut.Length));
}
}
}
}
public class BackgroundworkerTest
{
private int worker = 0;
public object ExclusiveLock = 0;
public int Worker
{
get { return this.worker; }
}
public void WalkDir(string dir)
{
// Exclusive write on console
lock (this.ExclusiveLock)
{
Console.CursorTop = 1; Console.CursorLeft = 1;
var consoleOut = string.Format("Directory={0}", dir);
Console.Write("{0}{1}", consoleOut, new string(' ', Console.WindowWidth*3 - consoleOut.Length));
}
var currentDir = new System.IO.DirectoryInfo(dir);
DirectoryInfo[] directoryList = null;
try
{
directoryList = currentDir.GetDirectories();
}
catch (UnauthorizedAccessException unauthorizedAccessException)
{
// No access to this directory, so let's leave
return;
}
foreach (var directoryInfo in directoryList)
{
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += (sender, args) =>
{
// Make sure that this worker variable is not messed up
lock (this.ExclusiveLock)
{
worker--;
}
};
DirectoryInfo info = directoryInfo;
bw.DoWork += (sender, args) => this.WalkDir(info.FullName);
lock (this.ExclusiveLock)
{
// Make sure that this worker variable is not messed up
worker++;
}
bw.RunWorkerAsync();
}
}
}
j'ai utilisé Tâches avec un BackgroundWorker
vous pouvez créer n'importe quel nombre de tâches et les ajouter à une liste de tâches. Le worker démarre lorsqu'une tâche est ajoutée, redémarre si une tâche est ajoutée alors que le worker est inactif, et s'arrête une fois qu'il n'y a plus de tâches.
cela vous permettra de mettre à jour l'interface graphique de façon asynchrone autant que vous en avez besoin sans la geler.
cela fonctionne comme c'est pour moi.
// 'tasks' is simply List<Task> that includes events for adding objects
private ObservableCollection<Task> tasks = new ObservableCollection<Task>();
// this will asynchronously iterate through the list of tasks
private BackgroundWorker task_worker = new BackgroundWorker();
public Form1()
{
InitializeComponent();
// set up the event handlers
tasks.CollectionChanged += tasks_CollectionChanged;
task_worker.DoWork += task_worker_DoWork;
task_worker.RunWorkerCompleted += task_worker_RunWorkerCompleted;
task_worker.WorkerSupportsCancellation = true;
}
// ----------- worker events
void task_worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (tasks.Count != 0)
{
task_worker.RunWorkerAsync();
}
}
void task_worker_DoWork(object sender, DoWorkEventArgs e)
{
try
{
foreach (Task t in tasks)
{
t.RunSynchronously();
tasks.Remove(t);
}
}
catch
{
task_worker.CancelAsync();
}
}
// ------------- task event
// runs when a task is added to the list
void tasks_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (!task_worker.IsBusy)
{
task_worker.RunWorkerAsync();
}
}
maintenant, tout ce dont vous avez besoin est de créer une nouvelle tâche et de l'ajouter à la liste<>. Il sera exécuté par le travailleur dans l'ordre qu'il a été placé dans la Liste<>
Task t = new Task(() => {
// do something here
});
tasks.Add(t);
dans OpenCV existe fonction WaitKey. Ir permet de résoudre ce problème de cette façon:
while (this->backgroundWorker1->IsBusy) {
waitKey(10);
std::cout << "Wait for background process: " << std::endl;
}
this->backgroundWorker1->RunWorkerAsync();