Considérez une méthode 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 attendre qu'un BackgroundWorker soit terminé?
Dans le passé, les gens ont essayé:
while (_worker.IsBusy)
{
Sleep(100);
}
Mais ce blocage , car il IsBusy
n'est résolu qu'après la RunWorkerCompleted
gestion de l' événement, et cet événement ne peut pas être géré tant que l'application n'est pas inactive. L'application ne restera pas inactive tant que le worker n'aura pas terminé. (De plus, c'est une boucle occupée - dégoûtant.)
D'autres ont ajouté des suggestions de kludging dans:
while (_worker.IsBusy)
{
Application.DoEvents();
}
Le problème avec cela est que cela Application.DoEvents()
provoque le traitement des messages actuellement dans la file d'attente, ce qui provoque des problèmes de réentrée (.NET n'est pas rentrant).
J'espère utiliser une solution impliquant des objets de synchronisation d'événements, où le code attend un événement - que les RunWorkerCompleted
gestionnaires d'événements du travailleur définissent. 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 retour à l'impasse: le gestionnaire d'événements ne peut pas s'exécuter tant que l'application n'est pas inactive, et l'application ne sera pas inactive car elle attend un événement.
Alors, comment pouvez-vous attendre qu'un BackgroundWorker se termine?
Mise à jour Les gens semblent confus par cette question. Ils semblent penser que j'utiliserai BackgroundWorker comme:
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += MyWork;
worker.RunWorkerAsync();
WaitForWorkerToFinish(worker);
Ce n'est pas ça, ce n'est pas ce que je fais et ce n'est pas ce qu'on demande ici. Si tel était le cas, il ne servirait à rien d'utiliser un travailleur d'arrière-plan.
la source
((BackgroundWorker)sender).CancellationPending
pour obtenir l'événement d'annulationIl y a un problème avec cette réponse. L'interface utilisateur doit continuer à traiter les messages pendant que vous attendez, sinon elle ne sera pas repeinte, ce qui posera un problème si votre agent d'arrière-plan met beaucoup de temps à répondre à la demande d'annulation.
Une seconde faille est qu'elle
_resetEvent.Set()
ne sera jamais appelée si le thread de travail lève une exception - laissant le thread principal en attente indéfiniment - cependant cette faille pourrait facilement être corrigée avec un bloc try / finally.Une façon de faire est d'afficher une boîte de dialogue modale qui a un minuteur qui vérifie à plusieurs reprises si le travailleur d'arrière-plan a terminé son travail (ou a terminé l'annulation dans votre cas). Une fois le travail en arrière-plan terminé, la boîte de dialogue modale redonne le contrôle à votre application. L'utilisateur ne peut pas interagir avec l'interface utilisateur tant que cela ne se produit pas.
Une autre méthode (en supposant que vous ayez au maximum une fenêtre non modale ouverte) consiste à définir ActiveForm.Enabled = false, puis à effectuer une boucle sur Application, DoEvents jusqu'à ce que le travail en arrière-plan ait terminé l'annulation, après quoi vous pouvez redéfinir ActiveForm.Enabled = true.
la source
Presque tous sont déconcertés par la question et ne comprennent pas comment un travailleur est utilisé.
Considérez un gestionnaire d'événements RunWorkerComplete:
Et tout va bien.
Vient maintenant une situation où l'appelant doit abandonner le compte à rebours parce qu'il doit exécuter une autodestruction d'urgence de la fusée.
Et il y a aussi une situation où nous devons ouvrir les portes d'accès à la fusée, mais pas en faisant un compte à rebours:
Et enfin, nous devons vidanger la fusée, mais ce n'est pas autorisé pendant un compte à rebours:
Sans la possibilité d'attendre l'annulation d'un worker, nous devons déplacer les trois méthodes vers RunWorkerCompletedEvent:
Maintenant, je pourrais écrire mon code comme ça, mais je ne vais pas le faire. Je m'en fiche, je ne le suis tout simplement pas.
la source
Vous pouvez archiver le RunWorkerCompletedEventArgs dans le RunWorkerCompletedEventHandler pour voir quel était l'état. Succès, annulation ou erreur.
Mise à jour : pour voir si votre worker a appelé .CancelAsync () en utilisant ceci:
la source
Vous n'attendez pas que l'agent d'arrière-plan se termine. Cela va à peu près à l'encontre de l'objectif de lancer un thread séparé. Au lieu de cela, vous devez laisser votre méthode se terminer et déplacer tout code qui dépend de l'achèvement vers un autre endroit. Vous laissez le travailleur vous dire quand c'est fait et appelez ensuite tout code restant.
Si vous voulez attendre que quelque chose se termine, utilisez une construction de thread différente qui fournit un WaitHandle.
la source
Pourquoi ne pouvez-vous pas simplement vous lier à l'événement BackgroundWorker.RunWorkerCompleted. C'est un rappel qui "se produira lorsque l'opération d'arrière-plan est terminée, a été annulée ou a déclenché une exception."
la source
Je ne comprends pas pourquoi vous voudriez attendre la fin d'un BackgroundWorker; cela semble vraiment être l'exact opposé de la motivation de la classe.
Cependant, vous pouvez démarrer chaque méthode avec un appel à worker.IsBusy et les faire quitter si elle est en cours d'exécution.
la source
Je veux juste dire que je suis venu ici parce que j'ai besoin d'un travailleur d'arrière-plan pour attendre pendant que j'exécutais un processus asynchrone en boucle, ma solution était beaucoup plus facile que toutes ces autres choses ^^
Je me suis juste dit que je partagerais parce que c'est là que je me suis retrouvé en cherchant une solution. En outre, c'est mon premier article sur le débordement de pile, donc si c'est mauvais ou quoi que ce soit, j'adorerais les critiques! :)
la source
Hm peut-être que je ne comprends pas bien votre question.
Le backgroundworker appelle l'événement WorkerCompleted une fois que sa `` méthode de travail '' (la méthode / fonction / sous qui gère l' événement backgroundworker.doWork ) est terminée, il n'est donc pas nécessaire de vérifier si le BW est toujours en cours d'exécution. Si vous souhaitez arrêter votre worker, vérifiez la propriété d'annulation en attente dans votre «méthode de travail».
la source
Le flux de travail d'un
BackgroundWorker
objet vous oblige essentiellement à gérer l'RunWorkerCompleted
événement pour les cas d'utilisation d'exécution normale et d'annulation de l'utilisateur. C'est pourquoi la propriété RunWorkerCompletedEventArgs.Cancelled existe. Fondamentalement, cela nécessite que vous considériez votre méthode Cancel comme une méthode asynchrone en soi.Voici un exemple:
Si vous ne voulez vraiment pas que votre méthode se termine, je vous suggère de mettre un drapeau comme un
AutoResetEvent
sur un dérivéBackgroundWorker
, puis de le remplacerOnRunWorkerCompleted
pour définir le drapeau. C'est encore un peu kludgy cependant; Je recommanderais de traiter l'événement d'annulation comme une méthode asynchrone et de faire tout ce qu'il fait actuellement dans leRunWorkerCompleted
gestionnaire.la source
Je suis un peu en retard à la fête ici (environ 4 ans) mais qu'en est-il de la configuration d'un thread asynchrone qui peut gérer une boucle occupée sans verrouiller l'interface utilisateur, puis que le rappel de ce thread soit la confirmation que BackgroundWorker a terminé l'annulation ?
Quelque chose comme ça:
En gros, cela déclenche un autre thread à exécuter en arrière-plan qui attend juste dans sa boucle occupée pour voir si le
MyWorker
est terminé. Une fois l'MyWorker
annulation terminée, le thread se terminera et nous pouvons l'utiliserAsyncCallback
pour exécuter la méthode dont nous avons besoin pour suivre l'annulation réussie - cela fonctionnera comme un événement psuedo. Comme il est distinct du thread d'interface utilisateur, il ne verrouille pas l'interface pendant que nous attendons laMyWorker
fin de l'annulation. Si votre intention est vraiment de verrouiller et d'attendre l'annulation, cela ne vous sert à rien, mais si vous voulez juste attendre pour pouvoir démarrer un autre processus, cela fonctionne bien.la source
Je sais que c'est vraiment tard (5 ans) mais ce que vous cherchez c'est d'utiliser un Thread et un SynchronizationContext . Vous allez devoir rassembler les appels d'interface utilisateur vers le thread d'interface utilisateur "à la main" plutôt que de laisser le Framework le faire automatiquement.
Cela vous permet d'utiliser un fil de discussion que vous pouvez attendre si nécessaire.
la source
la source
La solution de Fredrik Kalseth à ce problème est la meilleure que j'ai trouvée jusqu'à présent. D'autres solutions utilisent
Application.DoEvent()
qui peuvent causer des problèmes ou tout simplement ne pas fonctionner. Permettez-moi de transformer sa solution en une classe réutilisable. Puisqu'ilBackgroundWorker
n'est pas scellé, nous pouvons en dériver notre classe:Avec des drapeaux et un verrouillage approprié, nous nous assurons que
_resetEvent.WaitOne()
cela n'est vraiment appelé que si un travail a été commencé, sinon il_resetEvent.Set();
pourrait ne jamais être appelé!Le try-finally garantit qu'il
_resetEvent.Set();
sera appelé, même si une exception doit se produire dans notre gestionnaire DoWork. Sinon, l'application pourrait se figer pour toujours lors de l'appelCancelSync
!Nous l'utiliserions comme ceci:
Vous pouvez également ajouter un gestionnaire à l'
RunWorkerCompleted
événement comme indiqué ici:BackgroundWorker Class (documentation Microsoft) .
la source
La fermeture du formulaire ferme mon fichier journal ouvert. Mon travailleur d'arrière-plan écrit ce fichier journal, je ne peux donc pas laisser
MainWin_FormClosing()
terminer jusqu'à ce que mon travailleur d'arrière-plan se termine. Si je n'attends pas que mon agent d'arrière-plan se termine, des exceptions se produisent.Pourquoi est-ce si difficile?
Un simple
Thread.Sleep(1500)
fonctionne, mais il retarde l'arrêt (s'il est trop long), ou provoque des exceptions (s'il est trop court).Pour arrêter juste après la fin de l'outil de travail en arrière-plan, utilisez simplement une variable. Cela fonctionne pour moi:
la source
Vous pouvez utiliser l'événement RunWorkerCompleted. Même si vous avez déjà ajouté un gestionnaire d'événements pour _worker, vous pouvez en ajouter un autre qu'ils exécuteront dans l'ordre dans lequel ils ont été ajoutés.
cela peut être utile si vous avez plusieurs raisons pour lesquelles une annulation peut se produire, rendant la logique d'un seul gestionnaire RunWorkerCompleted plus compliquée que vous ne le souhaitez. Par exemple, annulation lorsqu'un utilisateur tente de fermer le formulaire:
la source
J'utilise la
async
méthode etawait
j'attends que l'ouvrier termine son travail:et en
DoWork
méthode:Vous pouvez également encapsuler la
while
boucleDoWork
avectry ... catch
pour définir_isBusy
estfalse
sur exception. Ou, enregistrez simplement_worker.IsBusy
laStopAsync
boucle while.Voici un exemple de mise en œuvre complète:
Pour arrêter le worker et attendre qu'il s'exécute jusqu'à la fin:
Les problèmes avec cette méthode sont:
la source
oh mec, certains d'entre eux sont devenus ridiculement complexes. tout ce que vous avez à faire est de vérifier la propriété BackgroundWorker.CancellationPending dans le gestionnaire DoWork. vous pouvez le vérifier à tout moment. une fois qu'il est en attente, définissez e.Cancel = True et libérez la méthode.
// méthode ici private void Worker_DoWork (expéditeur d'objet, DoWorkEventArgs e) {BackgroundWorker bw = (expéditeur en tant que BackgroundWorker);
}
la source