2008-10-02 4 views
4

Voici la situation: Je développe une application simple avec la structure suivante:Ma forme ne présente pas correctement lorsqu'il est lancé à partir d'un autre thread

  • FormMain (point de démarrage)
  • FormNotification
  • CompleFunctions

À droite?

Eh bien, dans FormMain J'ai la fonction suivante:

private void DoItInNewThread(ParameterizedThreadStart pParameterizedThreadStart, object pParameters, ThreadPriority pThreadPriority) 
{ 
    Thread oThread = new Thread(pParameterizedThreadStart); 
    oThread.CurrentUICulture = Settings.Instance.Language; 
    oThread.IsBackground = true; 
    oThread.Priority = pThreadPriority; 
    oThread.Name = "μRemote: Background operation"; 
    oThread.Start(pParameters); 
} 

Donc, chaque fois que je dois appeler un temps méthode consommation situé sur ComplexFunctions Je fais ce qui suit:

// This is FormMain.cs 
string strSomeParameter = "lala"; 
DoItInNewThread(new ParameterizedThreadStart(ComplexFunctions.DoSomething), strSomeParameter, ThreadPriority.Normal); 

L'autre classe, FormNotification, est un formulaire qui affiche des informations sur le processus à l'utilisateur. Cette notification de formulaire peut être appelée à partir de FormMain ou de ComplexFunctions. Exemple:

// This is ComplexFunctions.cs 
public void DoSomething(string pSomeParameter) 
{ 
    // Imagine some time consuming task 
    FormNotification formNotif = new FormNotification(); 
    formNotif.Notify(); 
} 

FormNotify dispose d'une minuterie, de sorte que, au bout de 10 secondes ferme le formulaire. Je n'utilise pas formNotif.ShowDialog parce que je ne veux pas mettre l'accent sur ce formulaire. Vous pouvez vérifier this link pour voir ce que je fais dans Notify.

Ok, voici le problème: Quand j'appelle FormNotify de ComplexFunction qui est appelé à partir d'un autre thread dans FormMain ... ce FormNotify disparaît après quelques millisecondes. Il est le même effet que lorsque vous faites quelque chose comme ceci:

using(FormSomething formSomething = new FormSomething) 
{ 
    formSomething.Show(); 
} 

Comment éviter cela?

Ce sont des solutions possibles que je ne veux pas utiliser:

  • Utilisation Thread.Sleep (10000) dans FormNotify
  • Utilisation FormNotif.ShowDialog()

C'est un scénario simplifié (FormNotify fait d'autres choses fantaisistes qui restent pendant 10 secondes, mais elles ne sont pas pertinentes pour voir le problème).

Merci pour votre temps !!! Et s'il vous plaît, désolé mon anglais.

Répondre

5

Presque chaque bibliothèque GUI est conçue pour autoriser uniquement les appels qui modifient l'interface graphique à effectuer dans un thread unique désigné à cet effet (appelé le thread UI). Si vous êtes dans un autre thread, vous devez organiser l'appel pour modifier l'interface graphique à créer dans le thread d'interface utilisateur. Dans .NET, la méthode consiste à appeler Invoke (synchrone) ou BeginInvoke (asynchrone). L'appel Java Swing équivalent est invokeLater() - il existe des fonctions similaires dans presque toutes les bibliothèques GUI.

Il existe quelque chose appelé affinité de thread. Il y a deux threads dans une application WinForm, un pour le rendu et un pour la gestion de l'interface utilisateur. Vous ne traitez que le thread d'interface utilisateur. Le fil de rendu reste caché - s'exécute en arrière-plan. Les seuls objets créés sur le thread de l'interface utilisateur peuvent manipuler l'interface utilisateur. En effet, les objets ont une affinité de thread avec le thread de l'interface utilisateur. Depuis, vous essayez de mettre à jour l'interface utilisateur (afficher une notification) à partir d'un thread différent de celui de l'interface utilisateur. Dans votre thread de travail, définissez un délégué et faites en sorte que FormMain écoute cet événement. Dans le gestionnaire d'événements (define in FormMain), écrivez du code pour afficher le formulaire FormNotify.

Ignorez l'événement du thread de travail lorsque vous souhaitez afficher la notification.

Lorsqu'un thread autre que le thread créateur d'une commande tente d'accéder à l'une des méthodes ou propriétés de ce contrôle, cela entraîne souvent des résultats imprévisibles. Une activité de thread invalide courante est un appel sur le mauvais thread qui accède à la propriété Handle du contrôle. Définissez CheckForIllegalCrossThreadCalls sur true pour rechercher et diagnostiquer plus facilement cette activité de thread lors du débogage. Notez que les appels cross-thread illégaux provoquent toujours une exception lorsqu'une application est démarrée en dehors du débogueur.

Remarque: la définition de CheckForIllegalCrossThreadCalls pour effectuer ne doit être effectuée que dans DEBUGGIN SITUATIONS UNIQUEMENT. Des résultats imprévisibles se produiront et vous finirez par essayer de chasser les bugs que vous aurez une recherche difficile.

5

Vous n'êtes pas autorisé à effectuer des appels WinForms à partir d'autres threads. Regardez BeginInvoke dans le formulaire - vous pouvez appeler un délégué pour afficher le formulaire à partir du thread UI. Edit: À partir des commentaires (ne définissez pas CheckForIllegalCrossThreadCalls sur false).

Plus d'info Presque toutes les bibliothèques GUI est conçu pour permettre uniquement les appels qui changent l'interface graphique à effectuer dans un seul fil désigné à cet effet (appelé thread d'interface utilisateur). Si vous êtes dans un autre thread, vous devez organiser l'appel pour modifier l'interface graphique à créer dans le thread d'interface utilisateur. Dans .NET, la méthode consiste à appeler Invoke (synchrone) ou BeginInvoke (asynchrone). L'appel Java Swing équivalent est invokeLater() - il existe des fonctions similaires dans presque toutes les bibliothèques GUI.

+0

J'utilise cette directive (en FormMain): CheckForIllegalCrossThreadCalls = false; Est-ce cela dont vous parlez? –

+0

C'EST MAUVAIS MAUVAIS !!! N'utilisez jamais cette méthode. Le thread d'arrière-plan doit être utilisé pour effectuer le traitement, mais toutes les fenêtres doivent être appelées depuis le thread ui principal. – Micah

+0

Ok, maintenant je me sens comme un échantillon de "mauvaises pratiques" –

0

Utilisez l'appel API SetWindowPos pour vous assurer que votre formulaire de notification est la fenêtre la plus élevée. Ce message explique comment:

http://www.pinvoke.net/default.aspx/user32/SetWindowPos.html

+0

Ce n'est pas ce que je demande ici, mais de toute façon merci pour le lien. C'était la bonne réponse à ma question précédente. –

1

Il y a quelque chose appelé affinité fil. Il y a deux threads dans une application WinForm, un pour le rendu et un pour la gestion de l'interface utilisateur. Vous ne traitez que le thread d'interface utilisateur. Le fil de rendu reste caché - s'exécute en arrière-plan. Les seuls objets créés sur le thread de l'interface utilisateur peuvent manipuler l'interface utilisateur. En effet, les objets ont une affinité de thread avec le thread de l'interface utilisateur. Depuis, vous essayez de mettre à jour l'interface utilisateur (afficher une notification) à partir d'un thread différent de celui de l'interface utilisateur. Dans votre thread de travail, définissez un délégué et faites en sorte que FormMain écoute cet événement. Dans le gestionnaire d'événements (define in FormMain), écrivez du code pour afficher le formulaire FormNotify.

Ignorez l'événement du thread de travail lorsque vous souhaitez afficher la notification.

+0

Salut, je pense que je ne comprends pas vraiment votre réponse. Peut-être que je abuse de votre gentillesse, mais, pourriez-vous poster un échantillon s'il vous plaît? Merci! –

1

En supposant que vous avez le bouton sous la forme et que vous voulez ouvrir une autre forme Form1 lorsque l'utilisateur clique sur ce bouton

private void button1_Click(object sender, EventArgs e) 
    { 

     Thread t = new Thread(new ThreadStart(this.ShowForm1)); 
     t.Start(); 

    } 

Tout ce que vous devez faire est de vérifier la propriété InvokeRequired et si oui appelez la méthode Invoke de votre formulaire passant ShowForm1 déléguer, qui se terminera en appel récursif où InvokeRequired sera faux

delegate void Func(); 
    private void ShowForm1() 
    {    
     if (this.InvokeRequired) 
     { 
      Func f = new Func(ShowForm1); 
      this.Invoke(f); 
     } 
     else 
     { 
      Form1 form1 = new Form1(); 
      form1.Show(); 
     }    
    }