671 votes

L'opération inter-filière n'est pas valide : Accès au contrôle à partir d'un thread autre que celui où il a été créé.

J'ai un scénario. (Windows Forms, C#, .NET)

  1. Il y a un formulaire principal qui accueille un certain contrôle de l'utilisateur.
  2. Le contrôle utilisateur effectue des opérations lourdes sur les données, de sorte que si j'appelle directement la fonction UserControl_Load l'interface utilisateur devient non réactive pendant la durée d'exécution de la méthode de chargement.
  3. Pour résoudre ce problème, je charge les données sur un fil différent (en essayant de modifier le code existant le moins possible).
  4. J'ai utilisé un fil de travail en arrière-plan qui chargera les données et qui, une fois terminé, informera l'application qu'il a fait son travail.
  5. Maintenant, un vrai problème est apparu. Toute l'interface utilisateur (formulaire principal et ses contrôles d'utilisateur enfants) a été créée sur le thread principal primaire. Dans la méthode LOAD du usercontrol, je récupère des données basées sur les valeurs d'un contrôle (comme une boîte de texte) sur le userControl.

Le pseudo-code ressemblerait à ceci :

CODE 1

UserContrl1_LoadDataMethod()
{
    if (textbox1.text == "MyName") // This gives exception
    {
        //Load data corresponding to "MyName".
        //Populate a globale variable List<string> which will be binded to grid at some later stage.
    }
}

L'exception qu'il a donnée était

L'opération inter-filière n'est pas valide : Accès au contrôle depuis un thread autre que celui où il a été créé.

Pour en savoir plus, j'ai fait quelques recherches sur Google et une suggestion m'est apparue comme l'utilisation du code suivant

CODE 2

UserContrl1_LoadDataMethod()
{
    if (InvokeRequired) // Line #1
    {
        this.Invoke(new MethodInvoker(UserContrl1_LoadDataMethod));
        return;
    }

    if (textbox1.text == "MyName") // Now it wont give an exception
    {
    //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be binded to grid at some later stage
    }
}

MAIS MAIS MAIS... il semble que je sois de retour à la case départ. L'application est à nouveau devient non réactive. Cela semble être dû à l'exécution de la condition if de la ligne #1. La tâche de chargement est à nouveau effectuée par le thread parent et non par le troisième que j'ai créé.

Je ne sais pas si je l'ai bien ou mal perçu. Je suis novice en matière d'enfilage.

Comment puis-je résoudre ce problème et aussi quel est l'effet de l'exécution de la ligne 1 du bloc if ?

La situation est la suivante : Je veux charger des données dans une variable globale en fonction de la valeur d'un contrôle. Je ne veux pas changer la valeur d'un contrôle depuis le fil enfant. Je ne le ferai jamais à partir d'un thread enfant.

Il suffit donc d'accéder à la valeur pour que les données correspondantes puissent être récupérées dans la base de données.

0 votes

Pour mon cas particulier de cette erreur, j'ai trouvé la solution de contournement en utilisant un BackgroundWorker sur le formulaire pour gérer les parties du code nécessitant beaucoup de données. (i.e. mettre tout le code problématique dans la méthode backgroundWorker1_DoWork() et l'appeler via backgroundWorker1.RunWorkerAsync())... Ces deux sources m'ont orienté dans la bonne direction : stackoverflow.com/questions/4806742/ youtube.com/watch?v=MLrrbG6V1zM

482voto

Jeff Hubbard Points 5292

Conformément à Commentaire de mise à jour de Prerak K (supprimé depuis) :

Je suppose que je n'ai pas présenté la question correctement.

La situation est la suivante : Je veux charger des données dans une variable globale en fonction de la valeur d'un contrôle. Je ne veux pas modifier la valeur d'un contrôle à partir du fil enfant. Je ne le ferai jamais à partir d'un thread enfant.

Il suffit donc d'accéder à la valeur pour que les données correspondantes puissent être récupérées dans la base de données.

La solution que vous souhaitez alors devrait ressembler à ceci :

UserContrl1_LOadDataMethod()
{
    string name = "";
    if(textbox1.InvokeRequired)
    {
        textbox1.Invoke(new MethodInvoker(delegate { name = textbox1.text; }));
    }
    if(name == "MyName")
    {
        // do whatever
    }
}

Faites votre traitement sérieux dans le fil séparé avant vous tentez de revenir au fil de la commande. Par exemple :

UserContrl1_LOadDataMethod()
{
    if(textbox1.text=="MyName") //<<======Now it wont give exception**
    {
        //Load data correspondin to "MyName"
        //Populate a globale variable List<string> which will be
        //bound to grid at some later stage
        if(InvokeRequired)
        {
            // after we've done all the processing, 
            this.Invoke(new MethodInvoker(delegate {
                // load the control with the appropriate data
            }));
            return;
        }
    }
}

1 votes

Cela fait un moment que je n'ai pas fait de programmation C#, mais d'après l'article de MSDN et mes connaissances parcellaires, ça y ressemble.

1 votes

La différence est que BeginInvoke() est asynchrone alors que Invoke() s'exécute de manière synchrone. stackoverflow.com/questions/229554/

222voto

Ryszard Dżegan Points 4079

Modèle de file d'attente dans l'interface utilisateur

Veuillez lire le Modèle de filetage dans les applications d'interface utilisateur ( L'ancien lien VB est ici ) afin de comprendre les concepts de base. Le lien renvoie à une page qui décrit le modèle de threading de WPF. Cependant, Windows Forms utilise la même idée.

Le fil conducteur de l'interface utilisateur

  • Il n'y a qu'un seul thread (thread UI), qui est autorisé à accéder à Contrôle System.Windows.Forms et les membres de ses sous-classes.
  • Tentative d'accès à un membre de Contrôle System.Windows.Forms à partir d'un thread différent du thread de l'interface utilisateur provoquera une exception inter-filière.
  • Comme il n'y a qu'un seul thread, toutes les opérations de l'interface utilisateur sont mises en file d'attente comme éléments de travail dans ce thread :

enter image description here

enter image description here

Méthodes BeginInvoke et Invoke

  • L'overhead de calcul de la méthode invoquée devrait être faible, tout comme l'overhead de calcul des méthodes de gestion des événements, car le thread de l'interface utilisateur est utilisé à cet endroit - le même qui est responsable de la gestion des entrées de l'utilisateur. Peu importe si c'est System.Windows.Forms.Control.Invoke ou System.Windows.Forms.Control.BeginInvoke .
  • Pour effectuer une opération coûteuse, il faut toujours utiliser un thread séparé. Depuis .NET 2.0 Travailleur d'arrière-plan est dédié à l'exécution d'opérations informatiques coûteuses dans Windows Forms. Cependant, dans les nouvelles solutions, vous devriez utiliser le modèle async-await tel que décrit dans le document suivant ici .
  • Utilisez System.Windows.Forms.Control.Invoke ou System.Windows.Forms.Control.BeginInvoke uniquement pour mettre à jour une interface utilisateur. Si vous les utilisez pour des calculs lourds, votre application se bloquera :

enter image description here

Appeler

enter image description here

BeginInvoke

enter image description here

Solution de code

Lire les réponses à la question Comment mettre à jour l'interface graphique à partir d'un autre thread en C# ? . Pour C# 5.0 et .NET 4.5, la solution recommandée est la suivante ici .

76voto

Jon Skeet Points 692016

Vous voulez seulement utiliser Invoke ou BeginInvoke pour le travail minimal requis pour modifier l'interface utilisateur. Votre méthode "lourde" doit s'exécuter sur un autre thread (par exemple, par le biais de BackgroundWorker ) mais en utilisant Control.Invoke / Control.BeginInvoke juste pour mettre à jour l'interface utilisateur. De cette façon, votre thread UI sera libre de gérer les événements UI, etc.

Voir mon article sur l'enfilage pour un Exemple WinForms - bien que l'article ait été écrit avant BackgroundWorker est arrivé sur la scène, et je crains de ne pas l'avoir mis à jour à cet égard. BackgroundWorker simplifie simplement un peu le rappel.

0 votes

Ici, dans ma situation, je ne change même pas l'interface utilisateur. Je ne fais qu'accéder à ses valeurs actuelles à partir du fil d'exécution de l'enfant.

1 votes

Vous devez toujours passer par le thread de l'interface utilisateur, même pour accéder aux propriétés. Si votre méthode ne peut pas continuer tant que la valeur n'est pas accessible, vous pouvez utiliser un délégué qui renvoie la valeur. Mais oui, passez par le thread UI.

0 votes

Bonjour Jon, je crois que vous me mettez sur la bonne voie. Oui, j'ai besoin de la valeur sans laquelle je ne peux pas aller plus loin. Pourriez-vous m'éclairer sur l'utilisation d'un délégué qui renvoie une valeur ? Merci

45voto

Cookey Points 1191

J'ai eu ce problème avec le FileSystemWatcher et j'ai constaté que le code suivant a résolu le problème :

fsw.SynchronizingObject = this

Le contrôle utilise alors l'objet de formulaire actuel pour traiter les événements, et sera donc sur le même fil.

3 votes

Cela a sauvé mon bacon. En VB.NET, j'ai utilisé .SynchronizingObject = Me

11voto

CLaRGe Points 1055

Utilisez le code trouvé ici sur StackOverflow pour éliminer le besoin d'écrire un nouveau délégué chaque fois que vous voulez appeler Invoke.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X