OVH Cloud OVH Cloud

Problème de création d'un contrôle avec le multi-threading

6 réponses
Avatar
Ludovic SOEUR
Est-ce que quelqu'un a déjà essayé de créer des contrôles dans des threads
séparés ? J'ai un problème que je ne comprends pas.
Pour simplifier les explications, j'ai écrit ces quelques lignes pour
montrer un exemple du problème.
Ce programme ne fait rien d'autre que d'afficher un bouton "Start thread".
Lorsqu'il est enfoncé, un nouveau thread démarre dans lequel un usercontrol
est créé, affichant un nouveau bouton. C'est tout.
Tout fonctionne, mais si comme dans le code source je place la ligne (ligne
23)
userControl1.UselessFunction();
ça ne marche plus (ça bloque indéfiniment) et je dois tuer la tâche.
Cette fonction inutile ne fait rien d'autre que d'afficher le bouton !
Etrange, n'est-ce pas ?

J'ai essayé de déplacer la ligne après la ligne 24
Invoke(new AddControl_Delegate(AddControl),new object[1] {userControl1});
et dans ce cas, ca marche parfaitement !

Est-ce que quelqu'un pourrait m'aider s'il vous plait ?

Merci,
Ludovic SOEUR.

Voici le code source :

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

public class Form1 : Form {
[STAThread]
static void Main() {
Application.Run(new Form1());
}
public Form1() {
Button button=new Button();
button.Text = "Start thread";
button.Click += new System.EventHandler(button_Click);
Controls.Add(button);
}
private void button_Click(object sender, System.EventArgs e) {
(new Thread(new ThreadStart(StartThread))).Start();
}
private void StartThread() {
UserControl1 userControl1=new UserControl1();
userControl1.Top=24;
userControl1.UselessFunction();
Invoke(new AddControl_Delegate(AddControl),new object[1] {userControl1});
}
private delegate void AddControl_Delegate(UserControl1 control);
private void AddControl(UserControl1 control) {
Controls.Add(control);
}
}

public class UserControl1 : UserControl {
private Button button=new Button();
public UserControl1() {
button.Text = "Success";
Controls.Add(button);
}
public void UselessFunction() {
button.Visible=false;
button.Visible=true;
}
}

6 réponses

Avatar
Patrick Philippot
> J'ai essayé de déplacer la ligne après la ligne 24
Invoke(new AddControl_Delegate(AddControl),new object[1]
{userControl1}); et dans ce cas, ca marche parfaitement !

Est-ce que quelqu'un pourrait m'aider s'il vous plait ?



Bonjour,

Si vous remplacez le code de UselessFunction par, par exemple:
button.Text = "Autre texte";

le programme fonctionne. Le fait de manipuler la propriété Visible
*avant* que le contrôle ne soit effectivement installé dans un conteneur
(il n'a donc pas encore de parent) doit à mon avis générer des effets de
bord néfastes. En effet, manipuler cette propriété Visible avant que le
contrôle ne soit physiquement présent dans son conteneur n'a pas
vraiment de sens. Si le contrôle essaie de notifier son parent de son
nouvel état (afin que le conteneur soit rafraîchi), je suppose que ça ne
se passe pas bien.

Il faudrait désassembler le code de la propriété Visible ou de
VisibleChanged pour comprendre exactement ce qui se passe.

--
Patrick Philippot - Microsoft MVP
MainSoft Consulting Services
www.mainsoft.fr
Avatar
Ludovic SOEUR
Merci pour votre réponse mais...
l'exemple que j'ai choisi est volontairement simplifié pour faire un
programme qui paraît inutile mais qui montre clairement le problème. La
question n'est pas de savoir que le fait de mettre visible a true n'a pas de
sens quand le contrôle n'est pas physiquement présent dans son conteneur
(surtout que dans le cas de mon programme qui pose problème, pas celui de
l'exemple que j'ai fourni, ça a un sens très important) mais en fait de
comprendre ce qui se passe réellement et pourquoi ca plante.
Pour l'instant, j'ai fait en sorte de créer un liste dynamique de tous les
contrôles qui doivent passer à visible='true'. Ensuite, j'attache mes
contrôles, puis je passe à visible='true' toute la liste que j'avais
préparée auparavant. Ce 'patch' fonctionne et correspond à ce que je
comparais dans mon petit exemple au déplacement après la ligne 24 du invoke.
Mais je ne suis pas sûr du tout que ca ne va pas causer d'autres problème,
comme par exemple lors de la suppression d'un contrôle, d'un rajout, ou
autre. En fait, quoi qu'il en soit, même si mon astuce fonctionne, ce n'est
pas un fonctionnement normal et c'est qqch que je ne peux pas garder. En C++
par exemple, il n'y a pas de soucis pour un code similaire...de même en
Java....
Est-ce que ca viendrait du CLR ? Y-a-t-il une solution ? Faut-il attendre un
patch de Microsoft ?
Ou alors, est-ce que ca viendrait de mon code, de la façon de créer mes
contrôles ?

Merci de votre aide,

Ludovic Soeur.


"Patrick Philippot" a écrit dans le
message de news:
> J'ai essayé de déplacer la ligne après la ligne 24
> Invoke(new AddControl_Delegate(AddControl),new object[1]
> {userControl1}); et dans ce cas, ca marche parfaitement !
>
> Est-ce que quelqu'un pourrait m'aider s'il vous plait ?

Bonjour,

Si vous remplacez le code de UselessFunction par, par exemple:
button.Text = "Autre texte";

le programme fonctionne. Le fait de manipuler la propriété Visible
*avant* que le contrôle ne soit effectivement installé dans un conteneur
(il n'a donc pas encore de parent) doit à mon avis générer des effets de
bord néfastes. En effet, manipuler cette propriété Visible avant que le
contrôle ne soit physiquement présent dans son conteneur n'a pas
vraiment de sens. Si le contrôle essaie de notifier son parent de son
nouvel état (afin que le conteneur soit rafraîchi), je suppose que ça ne
se passe pas bien.

Il faudrait désassembler le code de la propriété Visible ou de
VisibleChanged pour comprendre exactement ce qui se passe.

--
Patrick Philippot - Microsoft MVP
MainSoft Consulting Services
www.mainsoft.fr




Avatar
Patrick Philippot
Ludovic SOEUR wrote:
Merci pour votre réponse mais...
l'exemple que j'ai choisi est volontairement simplifié pour faire un
programme qui paraît inutile mais qui montre clairement le problème.
La question n'est pas de savoir que le fait de mettre visible a true
n'a pas de sens quand le contrôle n'est pas physiquement présent dans
son conteneur (surtout que dans le cas de mon programme qui pose
problème, pas celui de l'exemple que j'ai fourni, ça a un sens très
important) mais en fait de comprendre ce qui se passe réellement et
pourquoi ca plante.



J'ai bien compris mais il me semble que j'ai donné une explication (qui
vaut ce qu'elle vaut). En décompilant le code correspondant à la
propriété Visible (SetVisibleCore), elle me semble d'ailleurs largement
confirmée. Quand vous modifiez cette propriété, le code manipule le
handle de la fenêtre du contrôle (indéterminé à ce moment), son parent,
etc... Rien de tout cela n'existe tant que vous n'avez pas attaché le
contrôle à la form. L'objet Control existe mais pas la fenêtre physique.

J'admets que ce code pourrait effectuer plus de contrôles mais encore
une fois, comme sémantiquement, manipuler la propriété Visible avant que
le contrôle ne soit attaché à un conteneur n'a pas vraiment de sens, ces
vérifications ont probablement été bypassées.

Le fix, c'est de ne toucher à cette propriété que lorsque les contrôles
sont attachés, c'est-à-dire quand les fenêtres existent réellement.

C'est clair, soit la doc devrait dire qu'il ne faut pas toucher à cette
propriété avant d'avoir attaché le contrôle, soit c'est un bug (la
lecture du code décompilé montre bien que tout se passe comme si on
considérait cet attachement comme acquis - ce qui n'est pas choquant
outre mesure).

--
Patrick Philippot - Microsoft MVP
MainSoft Consulting Services
www.mainsoft.fr
Avatar
Vko
Attention la manipulation de controles dans les Windows Form doit se faire
EXCLUSIVEMENT dans le thead principal, et vu que c'est pas marqué partout
dans l'MSDN on peut facilement se faire avoir.

Pour contourner cette restrictions il faut passer par des délégués, entres
autres MethodInvoker.

Extrait de l'MSDN concernant la méthode Control.Invode :
Si le handle du contrôle n'existe pas encore, cette méthode effectuera une
recherche dans la chaîne parente du contrôle jusqu'à ce qu'elle trouve un
contrôle ou un formulaire sans handle de fenêtre. Si aucun handle approprié
n'est trouvé, la méthode Invoke lèvera une exception. Les exceptions
déclenchées au cours de l'appel seront propagées vers l'appelant.

Remarque Quatre méthodes sur un contrôle peuvent être appelées sans
problème à partir de n'importe quel thread : Invoke, BeginInvoke, EndInvoke
et CreateGraphics. Pour tous les autres appels de méthode, vous devez
utiliser l'une des méthodes Invoke pour marshaler l'appel au thread du
contrôle.
Le délégué peut être une instance de EventHandler, dans ce cas, le paramètre
Sender contiendra ce contrôle, et le paramètre Event contiendra
EventArgs.Empty. Le délégué peut également être une instance de MethodInvoker
ou tout autre délégué qui accepte une liste de paramètres Void. Un appel à un
délégué EventHandler ou MethodInvoker sera plus rapide qu'un appel à un autre
type de délégué.



"Ludovic SOEUR" wrote:

Est-ce que quelqu'un a déjà essayé de créer des contrôles dans des threads
séparés ? J'ai un problème que je ne comprends pas.
Pour simplifier les explications, j'ai écrit ces quelques lignes pour
montrer un exemple du problème.
Ce programme ne fait rien d'autre que d'afficher un bouton "Start thread".
Lorsqu'il est enfoncé, un nouveau thread démarre dans lequel un usercontrol
est créé, affichant un nouveau bouton. C'est tout.
Tout fonctionne, mais si comme dans le code source je place la ligne (ligne
23)
userControl1.UselessFunction();
ça ne marche plus (ça bloque indéfiniment) et je dois tuer la tâche.
Cette fonction inutile ne fait rien d'autre que d'afficher le bouton !
Etrange, n'est-ce pas ?

J'ai essayé de déplacer la ligne après la ligne 24
Invoke(new AddControl_Delegate(AddControl),new object[1] {userControl1});
et dans ce cas, ca marche parfaitement !

Est-ce que quelqu'un pourrait m'aider s'il vous plait ?

Merci,
Ludovic SOEUR.

Voici le code source :

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;

public class Form1 : Form {
[STAThread]
static void Main() {
Application.Run(new Form1());
}
public Form1() {
Button button=new Button();
button.Text = "Start thread";
button.Click += new System.EventHandler(button_Click);
Controls.Add(button);
}
private void button_Click(object sender, System.EventArgs e) {
(new Thread(new ThreadStart(StartThread))).Start();
}
private void StartThread() {
UserControl1 userControl1=new UserControl1();
userControl1.Top$;
userControl1.UselessFunction();
Invoke(new AddControl_Delegate(AddControl),new object[1] {userControl1});
}
private delegate void AddControl_Delegate(UserControl1 control);
private void AddControl(UserControl1 control) {
Controls.Add(control);
}
}

public class UserControl1 : UserControl {
private Button button=new Button();
public UserControl1() {
button.Text = "Success";
Controls.Add(button);
}
public void UselessFunction() {
button.Visibleúlse;
button.Visible=true;
}
}





Avatar
Patrick Philippot
Vko wrote:
Extrait de l'MSDN concernant la méthode Control.Invode :
Si le handle du contrôle n'existe pas encore, cette méthode
effectuera une recherche dans la chaîne parente du contrôle jusqu'à
ce qu'elle trouve un contrôle ou un formulaire sans handle de
fenêtre. Si aucun handle approprié n'est trouvé, la méthode Invoke
lèvera une exception. Les exceptions déclenchées au cours de l'appel
seront propagées vers l'appelant.



Merci pour cette information qui vient donner à l'explication de départ
les éléments complémentaires qui lui manquaient. Ceci confirme bien le
problème créé par l'absence de parent. On pourrait à la limite imaginer
de positionner le parent avant l'appel mais là encore, la signification
de l'opération dépend du contexte et ne correspond pas en tous cas à une
situation courante.

--
Patrick Philippot - Microsoft MVP
MainSoft Consulting Services
www.mainsoft.fr
Avatar
Ludovic SOEUR
Merci à tous pour vos réponses.
Vous aviez raison, les contrôles doivent être créés dans le thread principal
grâce à Control.Invoke.
Mais, après pas mal de recherche, j'ai trouvé qu'il y avait quelquechose de
bien caché : avant d'ajouter un contrôle, il DOIT être créé avec la fonction
CreateControl dans le thread principal qui assure que tous les handles des
fenêtres appartiennent au thread principal.

Voici le code source de la fonction à appeler avec Invoke qui permet de
créer et d'attacher correctement le contrôle :
private UserControl1 CreateControl_Proc() {
UserControl1 uc=new UserControl1();
uc.CreateControl();
Controls.Add(uc);
return uc;
}

Merci pour vous réponses qui m'ont mis sur la voie pour trouver une
solution.

Ludovic Soeur.

"Vko" a écrit dans le message de
news:
Attention la manipulation de controles dans les Windows Form doit se faire
EXCLUSIVEMENT dans le thead principal, et vu que c'est pas marqué partout
dans l'MSDN on peut facilement se faire avoir.

Pour contourner cette restrictions il faut passer par des délégués, entres
autres MethodInvoker.

Extrait de l'MSDN concernant la méthode Control.Invode :
Si le handle du contrôle n'existe pas encore, cette méthode effectuera une
recherche dans la chaîne parente du contrôle jusqu'à ce qu'elle trouve un
contrôle ou un formulaire sans handle de fenêtre. Si aucun handle


approprié
n'est trouvé, la méthode Invoke lèvera une exception. Les exceptions
déclenchées au cours de l'appel seront propagées vers l'appelant.

Remarque Quatre méthodes sur un contrôle peuvent être appelées sans
problème à partir de n'importe quel thread : Invoke, BeginInvoke,


EndInvoke
et CreateGraphics. Pour tous les autres appels de méthode, vous devez
utiliser l'une des méthodes Invoke pour marshaler l'appel au thread du
contrôle.
Le délégué peut être une instance de EventHandler, dans ce cas, le


paramètre
Sender contiendra ce contrôle, et le paramètre Event contiendra
EventArgs.Empty. Le délégué peut également être une instance de


MethodInvoker
ou tout autre délégué qui accepte une liste de paramètres Void. Un appel à


un
délégué EventHandler ou MethodInvoker sera plus rapide qu'un appel à un


autre
type de délégué.



"Ludovic SOEUR" wrote:

> Est-ce que quelqu'un a déjà essayé de créer des contrôles dans des


threads
> séparés ? J'ai un problème que je ne comprends pas.
> Pour simplifier les explications, j'ai écrit ces quelques lignes pour
> montrer un exemple du problème.
> Ce programme ne fait rien d'autre que d'afficher un bouton "Start


thread".
> Lorsqu'il est enfoncé, un nouveau thread démarre dans lequel un


usercontrol
> est créé, affichant un nouveau bouton. C'est tout.
> Tout fonctionne, mais si comme dans le code source je place la ligne


(ligne
> 23)
> userControl1.UselessFunction();
> ça ne marche plus (ça bloque indéfiniment) et je dois tuer la tâche.
> Cette fonction inutile ne fait rien d'autre que d'afficher le bouton !
> Etrange, n'est-ce pas ?
>
> J'ai essayé de déplacer la ligne après la ligne 24
> Invoke(new AddControl_Delegate(AddControl),new object[1]


{userControl1});
> et dans ce cas, ca marche parfaitement !
>
> Est-ce que quelqu'un pourrait m'aider s'il vous plait ?
>
> Merci,
> Ludovic SOEUR.
>
> Voici le code source :
>
> using System;
> using System.Drawing;
> using System.Windows.Forms;
> using System.Threading;
>
> public class Form1 : Form {
> [STAThread]
> static void Main() {
> Application.Run(new Form1());
> }
> public Form1() {
> Button button=new Button();
> button.Text = "Start thread";
> button.Click += new System.EventHandler(button_Click);
> Controls.Add(button);
> }
> private void button_Click(object sender, System.EventArgs e) {
> (new Thread(new ThreadStart(StartThread))).Start();
> }
> private void StartThread() {
> UserControl1 userControl1=new UserControl1();
> userControl1.Top$;
> userControl1.UselessFunction();
> Invoke(new AddControl_Delegate(AddControl),new object[1]


{userControl1});
> }
> private delegate void AddControl_Delegate(UserControl1 control);
> private void AddControl(UserControl1 control) {
> Controls.Add(control);
> }
> }
>
> public class UserControl1 : UserControl {
> private Button button=new Button();
> public UserControl1() {
> button.Text = "Success";
> Controls.Add(button);
> }
> public void UselessFunction() {
> button.Visibleúlse;
> button.Visible=true;
> }
> }
>
>
>