40 votes

Comment attendre correctement jusqu'à ce que BackgroundWorker est terminée?

Observer le morceau de code suivant:

var handler = GetTheRightHandler();
var bw = new BackgroundWorker();
bw.RunWorkerCompleted += OnAsyncOperationCompleted;
bw.DoWork += OnDoWorkLoadChildren;
bw.RunWorkerAsync(handler);

Maintenant, supposons que je veux attendre d' bw finitions de travail. Quelle est la bonne façon de le faire?

Ma solution est:

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 examiné le remplacement de l' finished drapeau avec un événement de synchronisation, de le fixer dans l' RunWorkerCompleted de gestionnaire et de bloquer par la suite au lieu de faire de la alors le sommeil de la boucle.

Hélas, c'est faux, car le code peut s'exécuter dans le WPF ou WindowsForm contexte de synchronisation, dans lequel cas, je bloque le même thread que l' RunWorkerCompleted gestionnaire s'exécute sur, qui n'est clairement pas très intelligent.

Je voudrais savoir d'une meilleure solution.

Merci.

EDIT:

P. S.

  • L'exemple de code est donc artificiel, intentionnellement pour préciser ma question. Je suis parfaitement conscient de l'achèvement de rappel et pourtant, je veux savoir comment faire pour attendre jusqu'à l'achèvement. C'est ma question.
  • Je suis conscient de l' Thread.Join, Delegate.BeginInvoke, ThreadPool.QueueUserWorkItem, etc... La question est précisément à propos de BackgroundWorker.

EDIT 2:

OK, je suppose que ce sera beaucoup plus facile si j'explique le scénario.

J'ai une unité de la méthode d'essai, qui appelle certains de code asynchrone, qui à son tour en fin de compte s'engage dans un BackgroundWorker , à laquelle je suis capable de passer un gestionnaire d'achèvement. Tout le code est le mien, donc je peux changer la mise en œuvre, si je désire. Je ne vais pas, cependant, pour remplacer l' BackgroundWorker, parce qu'il utilise automatiquement le droit de contexte de synchronisation, de sorte que lorsque le code est invoquée sur un thread d'INTERFACE utilisateur à l'achèvement de rappel est appelée sur le même thread de l'INTERFACE utilisateur, ce qui est très bon.

De toute façon, il est possible que l'unité de la méthode de test des hits de la fin, avant le BW a terminé son travail, qui n'est pas bon. Je souhaite donc à 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.

46voto

JohannesH Points 3817

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: assurez-Vous que doneEvent.Set() est appelé, quoi qu'il arrive. Aussi, vous voudrez peut-être fournir l' doneEvent.WaitOne() avec un argument précisant un délai d'expiration.

Remarque: Ce code est à peu près une copie de Fredrik Kalseth réponse à une question similaire.

19voto

Azhar Khorasany Points 1404

À attendre qu'un thread d'arrière-plan (simple ou multiple), procédez de la manière suivante:

  1. Créer une Liste de tâches en arrière-plan que vous avez créé par programmation:

    private IList<BackgroundWorker> m_WorkersWithData = new List<BackgroundWorker>();
    
  2. Ajouter le fond de travailleur 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();
    
  3. Utiliser la fonction suivante pour attendre que tous les travailleurs dans 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.
    }
    

7voto

Jeff Wilcox Points 5572

BackgroundWorker est un événement d'achèvement. Au lieu d'attendre, appelez votre code restant chemin à partir du gestionnaire d'achèvement.

2voto

goughy000 Points 321

Cette question est vieux, mais je ne pense pas que l'auteur a eu la réponse qu'il cherchait.

C'est un peu sale, et c'est dans vb.NET mais fonctionne 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

2voto

Don Points 21

VB.NET

While BackgroundWorker1.IsBusy()
    Windows.Forms.Application.DoEvents()
End While

Vous pouvez utiliser cette chaîne d'événements multiples. (sudo code à 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")

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X