2008-10-14 8 views
6

J'écris une application WinForms qui a deux modes: console ou GUI. Trois projets au sein d'une même solution, un pour l'application console, un pour les formulaires d'interface utilisateur et le troisième pour conserver la logique selon laquelle les deux interfaces se connecteront également. L'application Console fonctionne parfaitement.Erreurs d'interface utilisateur d'inter-threads étranges

Un modèle qui contient les utilisateurs-sélections, il a une IList<T> où T est un objet local, Step, qui implémente INotifyPropertyChanged, donc dans l'interface utilisateur est montée sur ce à un DataGridView. Tout va bien à l'exécution, l'état initial des objets est reflété sur l'écran.

Chacun des objets Step est une tâche qui est effectuée à tour de rôle; certaines propriétés vont changer, reflétées dans IList et transmises à DataGridView.

Cette action dans les versions de l'interface utilisateur est effectuée en créant un BackgroundWorker relançant des événements dans l'interface utilisateur. Le Step le fait et génère un objet StepResult qui est un type énuméré indiquant un résultat (par exemple Running, NotRun, OK, NotOK, Caveat) et une chaîne pour indiquer un message (parce que l'étape a couru mais pas tout à fait comme prévu, c.-à-d. une mise en garde). Normalement, les actions impliquent une interaction avec la base de données, mais en mode débogage, je génère aléatoirement un résultat.

Si le message est nul, il n'y a jamais un problème, mais si je produis une réponse comme ceci:

StepResult returnvalue = new StepResult(stat, "completed with caveat") 

je reçois une erreur disant que le DataGridView était accessible à partir d'un fil autre que le fil il a été créé le. (Je passe ceci à travers un handler personnalisé qui devrait gérer l'invocation si nécessaire - peut-être pas?)

Ensuite, si je génère une réponse unique, par ex. en utilisant un nombre aléatoire r:

StepResult returnvalue = new StepResult(stat, r.ToString()); 

les actions réussissent sans problème, les chiffres sont écrits proprement le DataGridView.

Je suis déconcerté. Je suppose que c'est en quelque sorte un problème littéral de chaîne, mais quelqu'un peut-il venir avec une explication plus claire?

Répondre

3

Puisque vous faites l'interface de liaison par abonnement d'événements, you might find this helpful ; C'est un exemple que j'ai écrit il y a quelques temps qui montre comment sous-classer BindingList<T> de sorte que les notifications sont rassemblées sur le thread UI automatiquement.

S'il n'y a pas de contexte de synchronisation (c.-à-d.mode console), il revient à l'invocation directe simple, donc il n'y a pas de surcharge. Lors de l'exécution dans le thread d'interface utilisateur, notez que cela utilise essentiellement Control.Invoke, qui exécute lui-même le délégué directement s'il se trouve sur le thread d'interface utilisateur. Donc, il n'y a qu'un seul commutateur si les données sont éditées à partir d'un thread non-interface utilisateur - fait ce que nous voulons ;-p

+0

Pas exactement la réponse - mais dans le bon sens! – Unsliced

+0

Perfick! Exactement ce dont j'avais besoin pour lier des collections dans une DLL à partir de clients Winforms et WPF, merci. – Wonko

+0

Cela fonctionne uniquement si BindingList a été créé dans le thread d'interface utilisateur, non? Mais existe-t-il une solution, si BindingList <> est créé en dehors du thread de l'interface utilisateur? Et non, nous ne pouvons pas passer ISynchronizeInvoke correspondant ou SynchronizationContext à BindingList :( –

4

Vous avez répondu à votre propre quesion: -

Je reçois une erreur disant que le DataGridView était accessible à partir d'un autre thread que le thread il a été créé.

WinForms que toutes les actions insiste effectuées sur des formulaires et des contrôles sont effectués dans le cadre du fil sous la forme a été créé. La raison est complexe, mais il a beaucoup à faire avec l'API Win32 sous-jacente. Pour plus de détails, voir les différentes entrées sur le blog The Old New Thing.

Ce que vous devez faire est d'utiliser la InvokeRequired et Invoke méthodes pour faire en sorte que les contrôles sont toujours accessibles à partir du même fil (pseudocodeish):

object Form.SomeFunction (args) 
{ 
    if (InvokeRequired) 
    { 
    return Invoke (new delegate (Form.Somefunction), args); 
    } 
    else 
    { 
    return result_of_some_action; 
    } 
} 
+0

J'apprécie la situation InvokeRequired, mais pourquoi ça marche quand je passe dans une chaîne unique, mais pas lors du passage dans une chaîne câblée? – Unsliced

+0

Les deux instructions données sont-elles appelées du même endroit dans le même contexte? – Skizz

+0

Oui. Ces deux exemples dans la question sont au même endroit - si j'utilise un SUCCESS! Si j'utilise l'autre - FAILURE! Et si le message n'est pas touché, mais seulement le statut mis à jour, il n'y a pas d'erreur (mais la grille est mise à jour). Je suis encore confus! – Unsliced

0

Je trouve cet article - « Updating IBindingList from different thread » - qui a pointé le doigt du blâme à la BindingList -

Parce que le BindingList n'est pas configuré pour les opérations asynchrones, vous devez mettre à jour le BindingList de le même fil sur lequel il était contrôlé.

passant Explicitement la forme mère comme un objet ISynchronizeInvoke et la création d'une enveloppe pour le BindingList<T> a fait l'affaire.

Questions connexes