2010-01-08 5 views
5

J'utilise un BackgroundWorker pour effectuer un calcul long (un seul de ces calculs à la fois).Annulation d'un BackgroundWorker, comment empêcher la course quand il a déjà fini

L'utilisateur a la possibilité d'annuler le travailleur (appelant worker.CancelAsync).

Dans la méthode worker.DoWork je vérifie périodiquement pour le drapeau annuler en attente et retour de la méthode.

Ensuite, l'événement Terminé est déclenché par le travailleur et je peux vérifier que le travailleur a été annulé. De plus, et c'est la chose importante, je fais un nettoyage supplémentaire quand une annulation est détectée.

Je suis sûr qu'il pourrait y avoir problème si l'utilisateur annule le travailleur et il est déjà retourné par la méthode DoWork. Dans ce cas, je voudrais vraiment savoir que le travailleur a été annulé pour que je puisse le nettoyage ...

est-il une meilleure façon de gérer la procédure annuler, avec le nettoyage, d'un travailleur?

Répondre

2

Votre gestionnaire d'événements DoWork shoud périodiquement vérifier BackgroundWorker.CancellationPending et mettre DoWorkEventArgs.Cancel true avant de revenir si elle a été annulée.

Votre gestionnaire d'événements RunWorkerCompleted doit vérifier la propriété RunWorkerCompletedEventArgs.Cancelled pour déterminer si le gestionnaire d'événements DoWork annulé (mis DoWorkEventArgs.Cancel à true).

En cas de condition de concurrence, il peut arriver que l'utilisateur demande l'annulation (BackgroundWorker.CancellationPending est vrai) mais que le travailleur ne l'a pas vu (RunWorkerCompletedEventArgs.Cancelled est faux). Vous pouvez tester ces deux propriétés pour déterminer que cela s'est produit et faire ce que vous voulez (soit le traiter comme une réussite - parce que le travail s'est terminé avec succès, ou comme une annulation - parce que l'utilisateur a annulé et ne se soucie pas plus).

Je ne vois aucune situation où il y a une ambiguïté sur ce qui s'est passé.

EDIT

En réponse au commentaire - s'il y a plusieurs classes qui ont besoin pour détecter CancellationPending, il n'y a pas vraiment pas d'alternative à passer à ces classes une référence à un type tel que BackgroundWorker qui leur permet de récupérer cette information. Vous pouvez faire abstraction de ceci dans une interface, ce que je fais généralement comme décrit dans cette réponse à une question sur BackgroundWorkers. Mais vous devez toujours passer une référence à un type qui implémente cette interface dans vos classes de travail. Si vous souhaitez que vos classes de travailleurs puissent définir DoWorkEventArgs.Cancel, vous devrez passer une référence à cette classe ou adopter une convention différente (par exemple, une valeur de retour booléenne ou une exception personnalisée) qui permet à vos classes de travailleurs d'indiquer cette annulation a eu lieu.

+1

C'est une explication très claire. Cependant, puisque le travail ne se fait pas uniquement dans la méthode "DoWork", je devrais passer le EventArgs à chaque classe qui vérifierait l'annulation et j'ai environ 30 de ces classes (problème d'optimisation profonde). C'est faisable mais je pense que ce serait un PITA ... Aussi je n'aime pas que ces classes doivent gérer l'annulation. Je cherche toujours un meilleur moyen. –

+0

"Aussi je n'aime pas que ces classes doivent gérer l'annulation" - Je ne comprends pas votre inquiétude. Si c'est le cas, ne les faites pas gérer l'annulation! Au lieu de cela, vérifiez uniquement l'annulation lorsque l'une de ces classes renvoie le contrôle à votre méthode DoWork principale. – Joe

+0

Ces classes internes effectuent un calcul très long et ne retournent donc pas souvent. –

0

Y at-il une raison que vous ne pouvez pas faire le nettoyage à la fin de la méthode que vous exécutez en mode asynchrone?

J'utilise généralement une structure comme celle-

private void MyThreadMethod() 
{ 
    try 
    { 
     // Thread code 
    } 
    finally 
    { 
     // Cleanup 
    } 
} 

Je n'utiliser que l'événement complet pour effectuer des tâches comme mettre à jour l'interface utilisateur, de ne pas nettoyer après le fil.

+1

Oui, je ne peux pas faire le nettoyage dans le travailleur d'arrière-plan ... Je dois le faire sur le fil de l'interface utilisateur. –

+1

Est-ce que vous voyez le feu de l'événement plus d'une fois? Si tel est le cas, vous pouvez utiliser un bloc lock (...) {} dans votre gestionnaire d'événements pour vous assurer que le code ne peut être exécuté qu'une fois à la fois et définir un indicateur indiquant que le nettoyage est terminé. verrouiller le bloc et exécuter uniquement le code de nettoyage s'il n'est pas défini). –

0

Sans voir votre code, il est un peu difficile de faire des suggestions, mais une chose que vous pouvez faire est de désactiver le bouton "Annuler" lorsque l'utilisateur clique dessus et aussi à la sortie de la méthode DoWork.

Si l'utilisateur peut également annuler en appuyant sur une touche, vous devrez également le désactiver.

De même, si la méthode DoWork est terminée, rien ne doit être annulé pour l'utilisateur - ou est-ce que quelque chose me manque? Dans ce cas, même si vous ne désactivez pas le bouton d'annulation, vous n'avez rien à ajouter. Pourquoi avez-vous besoin de voir le drapeau d'annulation après que le travailleur a fait son travail? Voulez-vous toujours annuler ce qui a déjà été changé?

+0

En fait, le problème n'empêche pas l'utilisateur d'annuler plusieurs fois ... Le problème est plus de s'assurer que je vois le drapeau d'annulation même APRES que le travailleur a fait son travail et que l'événement Terminé est reçu. –

1

vous êtes assuré de ne pas avoir une course dans le gestionnaire d'événements RunWorkerCompleted car il fonctionne sur le thread d'interface utilisateur. Cela devrait toujours fonctionner:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { 
    var bgw = sender as BackgroundWorker; 
    if (e.Cancelled || bgw.CancellationPending) { 
    // etc.. 
    } 
} 
+0

Ne semble pas fonctionner. Voir mon commentaire sur la réponse ci-dessus. – user12861

2

Je ne pense pas que les autres réponses ici résolvent réellement la condition de course possible dans tous les cas. Le problème est que CancellationPending semble être replacé sur false après la fin de DoWork, que DoWork ait réellement ou non une chance d'examiner le drapeau. Vous pouvez le tester vous-même si vous le souhaitez. Pour contourner ce problème, je devais créer une sous-classe, comme ceci:

Public Class CancelTrackingBackgroundWorker 
    Inherits System.ComponentModel.BackgroundWorker 
    Public CancelRequested As Boolean = False 
    Public Sub TrackableCancelAsync() 
    Me.CancelRequested = True 
    Me.CancelAsync() 
    End Sub 
End Class 

(Comment ennuyeux CancelAsync n'est pas Overridable, en passant.) J'appelle TrackableCancelAsync au lieu de l'ancien CancelAsync et je vérifier ce nouvel indicateur CancelRequested dans mon code. Tant que vous appelez et vérifiez seulement à partir du fil de l'interface utilisateur (comme devrait être standard), cela devrait prendre soin de vos problèmes de threading.

Questions connexes