BackgroundWorker OnWorkCompleted jette cross-thread exception
J'ai un simple contrôle utilisateur pour la base de données de pagination, qui utilise un contrôleur pour effectuer le DAL appels. J'utilise un BackgroundWorker
pour effectuer le levage lourd, et sur la OnWorkCompleted
cas, je ré-activer les boutons, changer un TextBox.Text
propriété et le déclenchement d'un événement pour le formulaire parent.
Forme d'Un détient mon UserControl. Quand j'ai cliquer sur un bouton qui ouvre le formulaire B, même si je ne fais rien "il" et juste à la fermer, et essayer de le faire dans la page suivante à partir de ma base de données, le OnWorkCompleted
est appelée sur le thread de travail (et non pas mon thread Principal), et jette un cross-thread exception.
Pour le moment, j'ai ajouté une case pour InvokeRequired
au gestionnaire de là, mais ce n'est pas le point de l'ensemble de OnWorkCompleted
est d'être appelé sur le thread Principal? Pourquoi ne serait-il pas fonctionner comme prévu?
EDIT:
J'ai réussi à cerner le problème à arcgis et BackgroundWorker
. J'ai la solution suivante qui ajoute une Commande à arcmap, qui s'ouvre d'un simple Form1
avec deux boutons.
Le premier bouton exécute une BackgroundWorker
qui dort pour 500ms et les mises à jour d'un compteur.
Dans le RunWorkerCompleted
méthode il vérifie InvokeRequired
, et met à jour le titre pour montrer whethever la méthode a été initialement s'exécute dans le thread principal ou le thread de travail.
Le deuxième bouton ouvre juste Form2
, qui ne contient rien.
Au premier abord, tous les appels à RunWorkerCompletedare
sont réalisées à l'intérieur du thread principal (Comme prévu - c'est le whold point de la RunWorkerComplete méthode, Au moins par ce que je comprends de la MSDN sur BackgroundWorker
)
Après l'ouverture et la fermeture Form2
, le RunWorkerCompleted
est toujours appelée sur le thread de travail. Je tiens à ajouter que je peux juste laisser cette solution pour le problème tel qu'il est (case pour InvokeRequired
dans le RunWorkerCompleted
méthode), mais je veux comprendre pourquoi il se passe à l'encontre de mes attentes. Dans mon "vrai" code que j'aimerais toujours savoir que la RunWorkerCompleted
méthode est appelée sur le thread principal.
J'ai réussi à cerner le point de le problème à la form.Show();
commande dans mon BackgroundTesterBtn
- si j'utilise ShowDialog()
au lieu de cela, 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, de sorte que l'utilisateur ne sera pas lié à la forme.
J'ai aussi essayé de reproduire le bug sur un projet WinForms. J'ai ajouté un simple projet qui ouvre la première forme sans ArcMap, mais dans ce cas je n'arrivais pas à reproduire le bug - RunWorkerCompleted
couru sur le thread principal, que j'ai utilisé Show()
ou ShowDialog()
, avant et après l'ouverture de Form2
. J'ai essayé d'ajouter une troisième forme d'agir en tant que forme principale avant mon Form1
, mais il n'a pas changé le résultat.
Ici est mon simple sln (VS2005sp1) - il faut
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
OriginalL'auteur Noam Gal | 2009-05-04
Vous devez vous connecter pour publier un commentaire.
Il ressemble à un bug:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
Alors je vous suggérons d'utiliser le pare-balles (pseudo-code):
OriginalL'auteur Max Galkin
Non, il n'est pas.
Vous ne pouvez pas simplement aller courir importe quoi, sur n'importe quel vieux thread. Les Threads ne sont pas polis objets que vous pouvez simplement dire "de l'exécuter, s'il vous plaît".
Un meilleur modèle mental d'un thread est un train de marchandises. Une fois ça va, c'est sur que c'est une piste. Vous ne pouvez pas en modifier le cours ou l'arrêter. Si vous voulez influencer, vous pouvez soit attendre jusqu'à ce qu'il arrive à la prochaine gare (ex: avoir vérifier manuellement pour certains événements), ou faire dérailler (
Thread.Abort
et CrossThread exceptions ont les mêmes conséquences que de faire dérailler un train... méfiez-vous!).Winforms des contrôles sorte de soutenir ce comportement (Ils ont
Control.BeginInvoke
qui vous permet d'exécuter n'importe quelle fonction sur le thread d'INTERFACE utilisateur), mais qui ne fonctionne que parce qu'ils ont un crochet spécial dans l'INTERFACE utilisateur windows le message de la pompe et de l'écriture de certains gestionnaires. Pour aller avec l'analogie ci-dessus, leur train vérifications à la gare et cherche de nouvelles directions périodiquement, et vous pouvez utiliser les installations de poster vos propres directions.La
BackgroundWorker
est conçu pour être d'usage général (il ne peut pas être liée à l'interface utilisateur windows) donc il ne peut pas utiliser le windowsControl.BeginInvoke
fonctionnalités. Il a supposer que votre thread principal est une imparable du "train" de faire sa propre chose, de sorte que l'événement terminé doit exécuter dans le thread de travail ou pas du tout.Cependant, comme vous êtes à l'aide de winforms, dans votre
OnWorkCompleted
gestionnaire, vous pouvez obtenir la Fenêtre pour exécuter un autre rappel à l'aide duBeginInvoke
fonctionnalité je l'ai mentionné ci-dessus. Comme ceci:Aussi, n'oubliez pas ceci:
C'est intéressant... j'ai créé une application de test (application console) avec un arrière-plan simple travailleur, et il a tiré dans le thread de travail. Peut-être le BackgroundWorker constructeur a un peu de magie en cours qui ont fait modifier son comportement, si elle est créée sur un thread d'INTERFACE utilisateur ou pas??
OriginalL'auteur Orion Edwards
La
BackgroundWorker
vérifie si l'instance de délégué, les points d'une classe qui prend en charge l'interfaceISynchronizeInvoke
. Votre couche DAL probablement ne pas implémenter cette interface. Normalement, vous devez utiliser leBackgroundWorker
sur unForm
, qui prend en charge l'interface.Dans le cas où vous souhaitez utiliser le
BackgroundWorker
de la couche DAL et souhaitez mettre à jour l'INTERFACE utilisateur à partir de là, vous avez trois options:Invoke
méthodeISynchronizeInvoke
sur le DAL de classe, et de rediriger les appels manuellement (c'est seulement trois méthodes et une propriété)BackgroundWorker
(donc, sur le thread d'INTERFACE utilisateur), à l'appelSynchronizationContext.Current
et enregistrer le contenu de l'instance dans une variable d'instance. LeSynchronizationContext
vous donnera alors leSend
méthode, qui va exactement de faire ce queInvoke
.OriginalL'auteur tofi9
La meilleure approche pour éviter les problèmes avec la croix-threading dans le GUI est à utilisation SynchronizationContext.
OriginalL'auteur Igor Brejc