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 sur BackgroundWorker .

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.

38
demandé sur abatishchev 2009-08-26 11:59:36

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 .

41
répondu JohannesH 2017-05-23 12:10:22

pour attendre un fil ouvrier d'arrière-plan (simple ou multiple) faire ce qui suit:

  1. 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>();
    
  2. 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();
    
  3. 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.
    }
    
17
répondu Azhar Khorasany 2011-10-11 11:38:03

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

7
répondu Jeff Wilcox 2009-08-26 08:04:09

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
2
répondu goughy000 2011-12-02 14:08:53

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")
2
répondu Don 2014-02-04 20:03:52

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");
}
2
répondu KevinBui 2015-03-19 04:47:10

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
}
0
répondu RvdK 2009-08-26 12:03:23

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();
        }
    }
}
0
répondu be981 2014-09-22 15:18:06

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);
0
répondu Beefjeff 2017-01-20 13:59:38

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();
-1
répondu csc 2014-05-07 11:37:45