web-dev-qa-db-fra.com

Invoke ou BeginInvoke ne peuvent pas être appelés sur un contrôle tant que le descripteur de fenêtre n'a pas été créé.

J'ai une méthode d'extension SafeInvoke Control similaire à celle Greg D discute ici (moins la vérification IsHandleCreated). 

Je l’appelle à partir d’un System.Windows.Forms.Form comme suit:

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}

Parfois (cet appel peut provenir de plusieurs threads), l'erreur suivante est générée: 

System.InvalidOperationException eu lieu

Message = "Invoke ou BeginInvoke ne peuvent pas être appelés sur un contrôle tant que le descripteur de fenêtre n'a pas été créé."

Source = "System.Windows.Forms"

StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) 
in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16

Qu'est-ce qui se passe et comment puis-je résoudre ce problème? Je sais que ce n'est pas un problème de création de formulaire, car parfois cela fonctionnera une fois et échouera la prochaine fois, alors quel pourrait être le problème?

PS. Je suis vraiment horrible à WinForms. Quelqu'un connaît-il une bonne série d’articles qui explique le modèle dans son ensemble et comment travailler avec?

70
George Mauer

Il est possible que vous créiez vos contrôles sur le mauvais thread. Considérez le documentation de MSDN suivant:

Cela signifie que InvokeRequired peut renvoie false si Invoke n'est pas requis (L'appel a lieu sur le même thread), ou si le contrôle a été créé sur un fil différent mais le contrôle est le descripteur n'a pas encore été créé.

Dans le cas où la poignée du contrôle n'a pas encore été créé, vous devriez pas simplement appeler propriétés, méthodes, ou des événements sur le contrôle. Cela pourrait provoquer la poignée du contrôle à être créé sur le fil d’arrière-plan, isoler le contrôle sur un thread sans un message pompe et rendant le application instable.

Vous pouvez vous protéger contre cette affaire en vérifiant également la valeur de IsHandleCreated lorsque InvokeRequired renvoie false sur un thread d'arrière-plan . Si la manette de contrôle n'a pas encore été créé, vous devez attendre jusqu’à ce qu’il ait été créé avant d'appeler Invoke ou BeginInvoke. Cela se produit généralement seulement si un fil de fond est créé dans le constructeur de la forme primaire pour l'application (comme dans Application.Run (new MainForm ()), avant l'affichage du formulaire ou si Application.Run a été appelé.

Voyons ce que cela signifie pour vous. (Il serait plus facile de raisonner si nous voyions également votre implémentation de SafeInvoke)

En supposant que votre implémentation soit identique à celle référencée à l'exception de la vérification sur IsHandleCreated , suivons la logique:

public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {    
        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

Prenons le cas où nous appelons SafeInvoke à partir du thread non-gui pour un contrôle dont le descripteur n'a pas été créé.

uiElement n'est pas null, nous vérifions donc uiElement.InvokeRequired. Selon les documents MSDN (en gras) InvokeRequired retournera false car, même s'il a été créé sur un autre thread, le descripteur n'a pas été créé! Cela nous envoie à la condition else où nous vérifions IsDisposed ou passons immédiatement à appeler l'action soumise ... à partir du fil de discussion en arrière-plan!

À ce stade, tous les paris sont désactivés sur ce contrôle car son descripteur a été créé sur un thread ne disposant pas de pompe de messages, comme indiqué dans le deuxième paragraphe. Peut-être que c'est le cas que vous rencontrez?

66
Greg D

J'ai trouvé la InvokeRequired pas fiable, alors j'utilise simplement

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}
32
Mathieu

Voici mon réponse à un question similaire _:

Je pense (pas encore tout à fait sûr) que En effet, InvokeRequired sera retourne toujours false si le contrôle a pas encore été chargé/montré. J'ai fait une solution de contournement qui semble fonctionner pour le moment, qui est trop simple référence le handle du associé control dans son créateur, comme suit:

var x = this.Handle; 

(Voir Http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html }

17
Benjol

La méthode dans le message que vous associez aux appels appelle Invoke/BeginInvoke avant de vérifier si le handle du contrôle a été créé dans le cas où il est appelé à partir d'un thread qui n'a pas créé le contrôle.

Ainsi, vous obtiendrez une exception lorsque votre méthode est appelée à partir d'un thread autre que celui qui a créé le contrôle. Cela peut se produire à partir d'événements distants ou d'éléments utilisateur de travail en file d'attente ...

MODIFIER

Si vous cochez InvokeRequired et HandleCreated avant d'appeler invoke, vous ne devriez pas obtenir cette exception.

5
Arnshea

Si vous allez utiliser une Control depuis un autre thread avant d'afficher ou de faire autre chose avec la Control, envisagez de forcer la création de son handle dans le constructeur. Ceci est fait en utilisant la fonction CreateHandle .

Dans un projet multi-thread, où la logique "contrôleur" n'est pas dans un formulaire WinForm, cette fonction est essentielle dans les constructeurs Control pour éviter cette erreur.

3

Référencez le handle du contrôle associé dans son créateur, comme suit:

Note : Méfiez-vous de cette solution. Si un contrôle a une poignée, il est beaucoup plus lent à faire des choses telles que définir la taille et l'emplacement de celle-ci. Cela rend InitializeComponent beaucoup plus lent. Une meilleure solution consiste à ne rien mettre en arrière-plan avant que le contrôle ait une poignée.

1
joe

Et ça :


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
0
Gourou Dsecours

Ajoutez ceci avant d'appeler la méthode invoke while (!this.IsHandleCreated) System.Threading.Thread.Sleep(100)

0
amos godwin
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();
0
Shimon Doodkin