BackgroundWorker OnWorkCompleted lève une exception de threading

J’ai un simple UserControl pour la pagination de firebase database, qui utilise un contrôleur pour effectuer les appels réels DAL. J’utilise BackgroundWorker pour effectuer le travail lourd et, lors de l’événement OnWorkCompleted , je réactive certains boutons, modifie une propriété TextBox.Text et TextBox.Text un événement pour le formulaire parent.

Le formulaire A contient mon UserControl. Lorsque je clique sur un bouton qui ouvre le formulaire B, même si je ne fais rien “là-bas” et que je le ferme, et que j’essaie de faire apparaître la page suivante de ma firebase database, le OnWorkCompleted est appelé sur le thread de travail (et non mon thread principal), et lève une exception cross-thread.

Pour le moment, j’ai ajouté un chèque pour InvokeRequired au gestionnaire, mais le but de OnWorkCompleted n’est-il pas d’appeler sur le thread principal? Pourquoi ça ne marcherait pas comme prévu?

MODIFIER:

J’ai réussi à limiter le problème à arcgis et à BackgroundWorker . J’ai la solution suivante qui ajoute une commande à arcmap, qui ouvre un simple Form1 avec deux boutons.

Le premier bouton exécute un BackgroundWorker qui dort 500 ms et met à jour un compteur. Dans la méthode RunWorkerCompleted , il recherche InvokeRequired et met à jour le titre pour indiquer que la méthode était exécutée à l’origine dans le thread principal ou dans le thread de travail. Le deuxième bouton ouvre simplement Form2 , qui ne contient rien.

Au début, tous les appels à RunWorkerCompletedare sont effectués à l’intérieur du fil principal (Comme prévu – c’est le point fort de la méthode RunWorkerComplete, Du moins d’après ce que j’ai compris de MSDN sur BackgroundWorker )

Après l’ouverture et la fermeture de Form2 , RunWorkerCompleted est toujours appelé sur le thread de travail. Je veux append que je peux simplement laisser cette solution au problème telle InvokeRequired (recherchez InvokeRequired dans la méthode RunWorkerCompleted ), mais je veux comprendre pourquoi cela se produit par rapport à mes attentes. Dans mon “vrai” code, j’aimerais toujours savoir que la méthode RunWorkerCompleted est appelée sur le thread principal.

J’ai réussi à cerner le problème à la form.Show(); commande dans mon BackgroundTesterBtn – si j’utilise ShowDialog() place, je n’ai pas de problème ( RunWorkerCompleted s’exécute toujours sur le thread principal). J’ai besoin d’utiliser Show() dans mon projet ArcMap pour que l’utilisateur ne soit pas lié au formulaire.

J’ai également essayé de reproduire le bogue sur un projet WinForms normal. J’ai ajouté un projet simple qui ouvre simplement le premier formulaire sans ArcMap, mais dans ce cas, je ne pouvais pas reproduire le bogue – RunWorkerCompleted exécuté sur le thread principal, que j’aie utilisé Show() ou ShowDialog() avant et après l’ouverture de Form2 . J’ai essayé d’append un troisième formulaire pour agir comme formulaire principal avant mon Form1 , mais cela n’a pas changé le résultat.

Voici mon sln simple (VS2005sp1) – il faut

ESRI.ArcGIS.ADF (9.2.4.1420)

ESRI.ArcGIS.ArcMapUI (9.2.3.1380)

ESRI.ArcGIS.SystemUI (9.2.3.1380)

    Cela ressemble à un bug:

    http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930

    http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx

    Donc, je suggère d’utiliser le pare-balles (pseudocode):

     if(control.InvokeRequired) control.Invoke(Action); else Action() 

    Le but de OnWorkCompleted n’est-il pas d’être appelé sur le fil principal? Pourquoi ça ne marcherait pas comme prévu?

    Non ce n’est pas.
    Vous ne pouvez pas simplement lancer une vieille chose sur un vieux fil. Les fils ne sont pas des objects polis pour lesquels vous pouvez simplement dire “exécutez ceci, s’il vous plaît”.

    Un meilleur modèle mental de fil est un train de marchandises. Une fois que c’est parti, c’est sur sa propre piste. Vous ne pouvez pas changer de cap ou l’arrêter. Si vous souhaitez l’influencer, vous devez attendre jusqu’à ce qu’elle arrive à la gare suivante (par exemple: le faire vérifier manuellement pour certains événements) ou le faire dérailler (les exceptions Thread.Abort et CrossThread ont à peu près les mêmes conséquences que de faire dérailler un événement). train … méfiez-vous!).

    Les contrôles Winforms prennent en charge ce comportement (ils ont Control.BeginInvoke qui permet d’exécuter n’importe quelle fonction sur le fil de l’UI), mais cela ne fonctionne que parce qu’ils ont un crochet spécial dans la pompe de messages de l’UI Windows et écrivent des gestionnaires spéciaux. Pour reprendre l’analogie ci-dessus, leur train s’enregistre à la gare et recherche périodiquement de nouvelles indications. Vous pouvez utiliser cette fonction pour l’afficher vous-même.

    BackgroundWorker est conçu pour un usage général (il ne peut pas être lié à l’interface graphique de Windows) et ne peut donc pas utiliser les fonctionnalités Windows Control.BeginInvoke . Il doit supposer que votre thread principal est un «train» imparable qui agit comme il se doit, donc l’événement terminé doit s’exécuter dans le thread de travail ou pas du tout.

    Toutefois, lorsque vous utilisez des formulaires Win, dans votre gestionnaire OnWorkCompleted , vous pouvez faire en sorte que la fenêtre exécute un autre rappel en utilisant la fonctionnalité BeginInvoke j’ai mentionnée ci-dessus. Comme ça:

     // Assume we're running in a windows forms button click so we have access to the // form object in the "this" variable. void OnButton_Click(object sender, EventArgs e ) var b = new BackgroundWorker(); b.DoWork += ... blah blah // attach an anonymous function to the completed event. // when this function fires in the worker thread, it will ask the form (this) // to execute the WorkCompleteCallback on the UI thread. // when the form has some spare time, it will run your function, and // you can do all the stuff that you want b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); } b.RunWorkerAsync(); // GO! } void WorkCompleteCallback() { Button.Enabled = false; //other stuff that only works in the UI thread } 

    De plus, n’oubliez pas ceci:

    Votre gestionnaire d’événements RunWorkerCompleted doit toujours vérifier les propriétés Error et Canceled avant d’accéder à la propriété Result. Si une exception a été déclenchée ou si l’opération a été annulée, l’access à la propriété Result déclenche une exception.

    BackgroundWorker vérifie si l’instance de délégué pointe vers une classe qui prend en charge l’interface ISynchronizeInvoke . Votre couche DAL n’implémente probablement pas cette interface. Normalement, vous utiliseriez BackgroundWorker sur un Form , qui prend en charge cette interface.

    Si vous souhaitez utiliser BackgroundWorker partir de la couche DAL et que vous souhaitez mettre à jour l’interface utilisateur à partir de là, vous avez trois options:

    • vous voudriez restr en appelant la méthode Invoke
    • implémente l’interface ISynchronizeInvoke sur la classe DAL et redirige les appels manuellement (il ne s’agit que de trois méthodes et d’une propriété)
    • Avant d’appeler BackgroundWorker (donc sur le thread d’interface utilisateur), appeler SynchronizationContext.Current et enregistrer l’instance de contenu dans une variable d’instance. Le SynchronizationContext vous donnera ensuite la méthode Send , qui fera exactement ce que Invoke fait.

    La meilleure approche pour éviter les problèmes d’inter-threading dans l’interface graphique consiste à utiliser SynchronizationContext .