126 votes

Comment attendre l'annulation d'un BackgroundWorker ?

Considérons un hypothétique d'un objet qui fait des choses pour vous :

public class DoesStuff
{
    BackgroundWorker _worker = new BackgroundWorker();

    ...

    public void CancelDoingStuff()
    {
        _worker.CancelAsync();

        //todo: Figure out a way to wait for BackgroundWorker to be cancelled.
    }
}

Comment peut-on attendre qu'un BackgroundWorker soit terminé ?


Dans le passé, les gens ont essayé :

while (_worker.IsBusy)
{
    Sleep(100);
}

Mais cela bloque parce que IsBusy n'est pas effacée avant que le RunWorkerCompleted est traité, et cet événement ne peut pas être traité tant que l'application n'est pas au repos. L'application ne sera pas inactive tant que le travailleur n'aura pas terminé. (De plus, c'est une boucle occupée - dégoûtant).

D'autres ont suggéré de l'ajouter :

while (_worker.IsBusy)
{
    Application.DoEvents();
}

Le problème avec ça, c'est que c'est Application.DoEvents() entraîne le traitement des messages actuellement dans la file d'attente, ce qui pose des problèmes de ré-entrance (.NET n'est pas ré-entrant).

J'espère pouvoir utiliser une solution impliquant des objets de synchronisation d'événements, où le code attend pour un événement - que le travailleur RunWorkerCompleted les jeux de gestionnaires d'événements. Quelque chose comme :

Event _workerDoneEvent = new WaitHandle();

public void CancelDoingStuff()
{
    _worker.CancelAsync();
    _workerDoneEvent.WaitOne();
}

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    _workerDoneEvent.SetEvent();
}

Mais je suis de nouveau dans l'impasse : le gestionnaire d'événements ne peut pas s'exécuter tant que l'application n'est pas au repos, et l'application ne se met pas au repos parce qu'elle attend un événement.

Alors comment attendre la fin d'un BackgroundWorker ?


Mise à jour Les gens semblent être désorientés par cette question. Ils semblent penser que je vais utiliser le BackgroundWorker comme.. :

BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);

C'est-à-dire pas c'est-à-dire pas ce que je fais, et c'est pas ce qui est demandé ici. Si c'était le cas, il n'y aurait aucun intérêt à utiliser un travailleur de fond.

129voto

Fredrik Kalseth Points 6633

Si je comprends bien votre demande, vous pourriez faire quelque chose comme ceci (le code n'a pas été testé, mais il montre l'idée générale) :

private BackgroundWorker worker = new BackgroundWorker();
private AutoResetEvent _resetEvent = new AutoResetEvent(false);

public Form1()
{
    InitializeComponent();

    worker.DoWork += worker_DoWork;
}

public void Cancel()
{
    worker.CancelAsync();
    _resetEvent.WaitOne(); // will block until _resetEvent.Set() call made
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    while(!e.Cancel)
    {
        // do something
    }

    _resetEvent.Set(); // signal that worker is done
}

8 votes

Cela bloquera l'interface utilisateur (par exemple, pas de repeints) si le travailleur d'arrière-plan prend beaucoup de temps pour s'annuler. Il est préférable d'utiliser l'une des méthodes suivantes pour arrêter l'interaction avec l'interface utilisateur : stackoverflow.com/questions/123661/

1 votes

+1 juste ce que le docteur a ordonné...bien que je sois d'accord avec @Joe si la demande d'annulation peut prendre plus d'une seconde.

0 votes

Que se passe-t-il lorsque le CancelAsync est traité avant le WaitOne ? Ou est-ce que le bouton d'annulation ne fonctionne qu'une seule fois.

15voto

Joe Points 60749

Il y a un problème avec ce réponse. L'interface utilisateur doit continuer à traiter les messages pendant que vous attendez, sinon elle ne se repeindra pas, ce qui sera un problème si votre travailleur d'arrière-plan prend beaucoup de temps pour répondre à la demande d'annulation.

Un deuxième défaut est que _resetEvent.Set() ne sera jamais appelé si le fil de travail lève une exception - laissant le fil principal attendre indéfiniment - cependant cette faille pourrait facilement être corrigée avec un bloc try/finally.

Une façon de procéder consiste à afficher une boîte de dialogue modale dotée d'une minuterie qui vérifie de manière répétée si le travailleur d'arrière-plan a terminé son travail (ou son annulation dans votre cas). Une fois que le travailleur d'arrière-plan a terminé, la boîte de dialogue modale renvoie le contrôle à votre application. L'utilisateur ne peut pas interagir avec l'interface utilisateur avant que cela ne se produise.

Une autre méthode (en supposant que vous ayez au maximum une fenêtre sans modèle ouverte) consiste à définir ActiveForm.Enabled = false, puis à boucler sur Application,DoEvents jusqu'à ce que le travailleur d'arrière-plan ait fini de s'annuler, après quoi vous pouvez définir ActiveForm.Enabled = true à nouveau.

5 votes

Cela peut être un problème, mais c'est fondamentalement accepté comme faisant partie de la question "Comment attendre pour l'annulation d'un BackgroundWorker". Attendre signifie que l'on attend, que l'on ne fait rien d'autre. Cela inclut également le traitement des messages. Si je ne voulais pas attendre le travailleur d'arrière-plan, je pourrais simplement appeler .CancelAsync. Mais ce n'est pas l'exigence de conception ici.

1 votes

+1 pour avoir signalé la valeur de la méthode CancelAsync, versets en attendant pour le travailleur d'arrière-plan.

8voto

Ian Boyd Points 50743

Vous êtes presque tous déroutés par la question et ne comprenez pas comment un travailleur est utilisé.

Considérons un gestionnaire d'événement RunWorkerComplete :

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;
}

Et tout va bien.

Il arrive maintenant une situation où l'appelant doit interrompre le compte à rebours parce qu'il doit exécuter une autodestruction d'urgence de la fusée.

private void BlowUpRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    StartClaxon();
    SelfDestruct();
}

Il arrive également que nous devions ouvrir les portes d'accès à la fusée, mais pas pendant le compte à rebours :

private void OpenAccessGates()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

Et enfin, nous devons désapprovisionner la fusée en carburant, mais ce n'est pas autorisé pendant le compte à rebours :

private void DrainRocket()
{
    if (worker != null)
    {
        worker.CancelAsync();
        WaitForWorkerToFinish(worker);
        worker = null;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Sans la possibilité d'attendre l'annulation d'un travailleur, nous devons déplacer ces trois méthodes vers le RunWorkerCompletedEvent :

private void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (!e.Cancelled)
    {
        rocketOnPad = false;
        label1.Text = "Rocket launch complete.";
    }
    else
    {
        rocketOnPad = true;
        label1.Text = "Rocket launch aborted.";
    }
    worker = null;

    if (delayedBlowUpRocket)
        BlowUpRocket();
    else if (delayedOpenAccessGates)
        OpenAccessGates();
    else if (delayedDrainRocket)
        DrainRocket();
}

private void BlowUpRocket()
{
    if (worker != null)
    {
        delayedBlowUpRocket = true;
        worker.CancelAsync();
        return;
    }

    StartClaxon();
    SelfDestruct();
}

private void OpenAccessGates()
{
    if (worker != null)
    {
        delayedOpenAccessGates = true;
        worker.CancelAsync();
        return;
    }

    if (!rocketOnPad)
        DisengageAllGateLatches();
}

private void DrainRocket()
{
    if (worker != null)
    {
        delayedDrainRocket = true;
        worker.CancelAsync();
        return;
    }

    if (rocketOnPad)
        OpenFuelValves();
}

Maintenant, je pourrais écrire mon code comme ça, mais je ne le ferai pas. Je m'en fiche, mais je ne le ferai pas.

21 votes

Où se trouve la méthode WaitForWorkerToFinish ? Un code source complet ?

4voto

Seb Nilsson Points 8619

Vous pouvez vous inscrire au RunWorkerCompletedEventArgs dans le RunWorkerCompletedEventHandler pour voir quel était le statut. Succès, annulation ou erreur.

private void RunWorkerCompletedEventHandler(sender object, RunWorkerCompletedEventArgs e)
{
    if(e.Cancelled)
    {
        Console.WriteLine("The worker was cancelled.");
    }
}

Mise à jour : Pour savoir si votre travailleur a appelé .CancelAsync() en utilisant ceci :

if (_worker.CancellationPending)
{
    Console.WriteLine("Cancellation is pending, no need to call CancelAsync again");
}

2 votes

L'exigence est que CancelDoingStuff() ne peut pas revenir avant que le travailleur soit terminé. Vérifier comment il s'est terminé n'est pas vraiment intéressant.

0 votes

Ensuite, vous devrez créer un événement. Cela n'a rien à voir avec BackgroundWorker en particulier, vous devez juste implémenter un événement, l'écouter et le déclencher lorsqu'il est terminé. Et RunWorkerCompletedEventHandler est le moment où c'est fait. Lance un autre évènement.

3voto

Rick Minerich Points 2407

Pourquoi ne pouvez-vous pas simplement vous connecter à l'événement BackgroundWorker.RunWorkerCompleted ? C'est un rappel qui "se produit lorsque l'opération d'arrière-plan est terminée, a été annulée ou a soulevé une exception".

1 votes

Parce que la personne qui utilise l'objet DoesStuff lui a demandé d'annuler. Les ressources partagées que l'objet utilise sont sur le point d'être déconnectées, retirées, éliminées, fermées, et il a besoin de savoir que c'est fait pour que je puisse continuer.

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