C# 2.0 comment tuer un thread secondaire avec la touche escape [Résolu]

avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 16 janv. 2008 à 16:53 - Dernière réponse : avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention
- 19 janv. 2008 à 17:55
Bonjour à tous,

J'ai parcouru inlassablement le site csharpfr.com en quête d'une réponse à mon problème, et il est vrai qu'on parle de temps à autre des threads, je tiens d'ailleurs à remercier notamment MorpionMx pour son excellent tuto sur les opérations cross threads qui sera sans doute utile à beaucoup de csharpiens (http://www.csharpfr.com/tutorial.aspx?id=174).
Je dois dire tout dabord que je débute en POO et C# et c'est mon premier post ! J'ai surtout utilisé VB (et notamment VBA) par le passé, alors svp, soyez indulgent !

Je souhaite pouvoir interrompre mon thread secondaire définitivement par la méthode 'Thread.Abort()', quand j'appuie sur la touche Escape. En fait, pour commencer à apprendre le c#, j'ai choisi de créer une visionneuse d'images (eh oui ! encore une !), dans laquelle j'essaie d'implémenter un modeste diaporama que je lance en cliquant sur un bouton. Je voudrais tout de même donner la possibilité à l'utilisateur d'arrêter le diaporama (et ce, à n'importe quel moment de son exécution) car s'il y a beaucoup d'images à visualiser, l'utilisateur peut se lasser ... et vouloir tout de suite récupérer la main pour faire autre chose (c'est-à-dire revenir sur le thread principal).

Mon diaporama ne fait que boucler, à l'aide d'un 'For' sur toutes les images contenues dans une 'imagelist' que j'affiche, au besoin, avec un 'listview'...
J'ai bien essayé de le faire sans utiliser de Thread secondaire, comme ceci :

private

bool CancelKeyPressed = false;

private
void btnExecDiapo_Click(
object sender,
EventArgs e)
{
   ...
   ExecDiapoAvecEffetAlpha();

}

protected
override
bool ProcessCmdKey(
ref
Message msg,
Keys keyData)
{   CancelKeyPressed keyData
Keys.Escape;
   return

true;
}

private

void ExecDiapoAvecEffetAlpha()
{
   For (int iSelActu = 0; iSélActu < lvw.Items.Count; iSelActu++)
   {
      // 'sSelActuFileName' me permet de renseigner mes méthode Exec_ImgApparait() et Exec_ImgDisparait() pour utiliser l'image actuelle.
      sSelActuFileName = lvwImageList2.Items[iSélActu].Text;
      // pour la petite histoire, j'utilise la classe ColorMatrix pour faire apparaitre et disparaitre l'image avec un effet alpha, si certaines
      // personnes sont intéressés, je posterai le détail de mon code pour mes méthodes Exec_ImgApparait() et Exec_ImgDisparait() ...
      Exec_ImgApparait();
      // je laisse le soin à l'utilisateur, préalablement, de paramétrer le temps (en millisecondes) qu'il veut pour visualiser chaque image,
      // grace à ' iTemps '.

      Thread.Sleep(iTemps);
      Exec_ImgDisparait();
      Application
.DoEvents();
      if
(CancelKeyPressed)
      {
         if
(
MessageBox.Show(
"Arrêter le diaporama ?",
"Demande d'arrêt du diaporama",
         MessageBoxButtons
.YesNo,
MessageBoxIcon.Question) ==
DialogResult.Yes)
         {
            CancelKeyPressed =
false;
           break
;
         }
      }
   }
}

Avec le code précédent, malheureusement, on ne peut annuler l'exécution de la
boucle QU'AU MOMENT où on a le 'Application.DoEvents();'  qui "rend la main à windows" lui donnant le temps de détecter si on a appuyé sur la touche 'Escape' (on ne peut donc pas arrêter le diaporama pendant le 'Thread.Sleep(iTemps);' par exemple.

C'est pourquoi, j'ai pensé utiliser un Thread secondaire (peut-être à tord !?)  pour gérer l'exécution du diaporama, dans l'espoir qu'un Thread.Abort() puisse l'arrêter définitivement, et surtout le plus rapidement possible, en codant ceci (merci encore à MorpionMx) :

private
Thread ThreadDiaporama;

private
delegate
void
delegateDiaporama();

private

void btnExecDiapo_Click(
object sender,
EventArgs e)
{
   ...
   ThreadDiaporama =
new
Thread(
new
ThreadStart(Exec_ThreadDiaporama));
   ThreadDiaporama.Start();
}

private

void Exec_ThreadDiaporama()
{
   
IAsyncResult ias =
this.BeginInvoke(
new
delegateDiaporama(ExecDiapoAvecEffetAlpha));
   // J'ai essayé un 'Application.DoEvents()' mais cela n'a pas marché.
   
//Application.DoEvents();
   if (CancelKeyPressed)
   {
      
if (
MessageBox.Show(
"Arrêter le diaporama ?",
"Demande d'arrêt du diaporama",
      
MessageBoxButtons.YesNo,
MessageBoxIcon.Question) ==
DialogResult.Yes)
      {
         CancelKeyPressed =
false;
         ThreadDiaporama.Abort();
      }
   }
   
this.EndInvoke(ias);
}

D'après le tuto de MorpionMx, (mais peut-être ai-je mal compris !) , les instructions se situant entre BeginInvoke et EndInvoke seront appelées sans attendre. Mais, chez moi, ce n'est pas le cas ! la touche Escape n'est pas détectée pendant l'exécution du ThreadDiaporama. J'ai même essayé d'exécuter le diaporama dans le thread principal et de faire le test 'if (CancelKeyPressed)' dans un thread secondaire; mais je rencontre toujours le problème.

Je sais que mes difficultés peuvent faire rire les érudits du Csharp, mais pour moi, c'est difficile ! Je n'ai peut-être rien compris à l'utilité des Threads !?

La seule question que je pose est donc simple : Comment faire en sorte que le ThreadDiaporama ne bloque pas toute l'application, ce qui me permettrait de faire le test de la touche Escape ?

Merci.

Avatar.
Afficher la suite 

Votre réponse

7 réponses

Meilleure réponse
MorpionMx 3489 Messages postés lundi 16 octobre 2000Date d'inscription 30 octobre 2008 Dernière intervention - 17 janv. 2008 à 20:09
3
Merci
Salut,

Je suis désolé, j'ai pas beaucoup beaucoup de temps :/
Je vais quand meme essayer de te donner une piste, qui me semble t il, pourrait être la cause de ton probleme : si tu as une boucle, qi se charge de faire "dérouler" le diaporama, ce n'est pas la méthode appelée par le delegate qui doit l'executer, mais le thread secondaire.

Je te donne 2 petits bouts de code en exemple qui t'illustreront la différence.
   Pour le premier, le KeyDown ne repond pas. la boucle se fait dans le delegate. 
   Pour le second, pas de souci, le KeyDown reagit instantanément, la boucle est dans le Thread.

(J'ai juste ajouté un bouton et un TextBox dans la form, le bouton se charge d'executer le Thread.)

Exemple 1
<hr />public

partial
class
Form1 :
Form
{

   private
Thread t;

   public Form1()
   {
      InitializeComponent();

      this.KeyPreview =
true;

      this.KeyDown +=
new
KeyEventHandler(Form1_KeyDown);
    }

   void Form1_KeyDown(
object sender,
KeyEventArgs e)
   {

      if (t !=
null && t.IsAlive) 
         t.Abort();
   }

   
   private
void button1_Click(
object sender,
EventArgs e)
   {

      if (t ==
null)
      {
         t =
new
Thread(
new
ThreadStart(execT));
         t.IsBackground =
true;
         t.Start();
         }
   }

   private
void execT()
   {

      IAsyncResult ias =
this.BeginInvoke(
new
monDelegate(MethodeTest));

      this.EndInvoke(ias);
    }

   
   private
void MethodeTest()
   {

      for (
int i = 0; i < 1000; i++)
      {

         Thread.Sleep(1000);

         this.textBox1.AppendText(i.ToString());
      }
    }

   private
delegate
void
monDelegate();
}

<hr />

Exemple 2
<hr />public

partial
class
Form1 :
Form
{

   private
Thread t;

   public Form1()
   {
      InitializeComponent();

      this.KeyPreview =
true;

      this.KeyDown +=
new
KeyEventHandler(Form1_KeyDown);
   }

   
void Form1_KeyDown(
object sender,
KeyEventArgs e)
   {

      if (t !=
null && t.IsAlive) 
         t.Abort();
   }

   private
void button1_Click(
object sender,
EventArgs e)
   {

      if (t ==
null)
      {
         t =
new
Thread(
new
ThreadStart(execT));
         t.IsBackground =
true;
         t.Start();
       }
   }

   
   private
void execT()
   {

      for (
int i = 0; i < 1000;  i++)
      {

         IAsyncResult ias =
this.BeginInvoke(
new
monDelegate(MethodeTest), i.ToString());

         Thread.Sleep(1000);

         this.EndInvoke(ias);
      }
    }

   private
void MethodeTest(
string text)
   {

      this.textBox1.AppendText(text);
   }

   
   private
delegate
void
monDelegate(
string text);

}
<hr />
J'ai un peu anticipé ton probleme, en esperant avoir vu juste. (Parce que si ce n'est pas le cas, ca va etre quasi impossible pour moi de t'aider avant la semaine prochaine)
Bon courage

Mx
MVP C# 

Merci MorpionMx 3

Avec quelques mots c'est encore mieux Ajouter un commentaire

Codes Sources a aidé 93 internautes ce mois-ci

Commenter la réponse de MorpionMx
MorpionMx 3489 Messages postés lundi 16 octobre 2000Date d'inscription 30 octobre 2008 Dernière intervention - 16 janv. 2008 à 20:28
0
Merci
Salut,

Merci pour tous ces remerciements

Petite question, d'ou vient ton boolean CancelKeyPressed ?

De mon point de vue, cette façon de proceder n'est pas la bonne. Tu dois t'inscrire a l'evenement KeyPressed de ta form, et arreter ton thread depuis la méthode associée a cet evenement (tonThread.Abort(), en verifiant avant s'il 'nest pas null et qu'il soit bien IsAlive = true).
Très important : Pense a mettre la propriété KeyPreview de ta form a true, sinon cela ne fonctionnera pas.

Et met la propriété IsBackground de ton thread a true aussi :)

Mx
MVP C# 
Commenter la réponse de MorpionMx
avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 16 janv. 2008 à 21:17
0
Merci
Quel rapidité ! Merci infiniment d'avoir répondu si vite MorpionMx !
Tu me demandes d'ou vient mon booléen CancelKeyPressed,
Je le déclare et l'initialise à False juste après ces 2 lignes :

namespace VisionneuseImg
{



   public
partial
class
formPleinEcran :


Form
   {
      (...)
      privatebool CancelKeyPressed = false;
      (...)



      //Fonction pour overrider ProcessCmdKey et détecter la touche Escape du clavier

      protected
override
bool ProcessCmdKey(refMessage msg, Keys keyData)
      {
         //et j'utilise ce booléen ici  :         CancelKeyPressed keyData Keys.Escape;
         returntrue;
      }
      (...)
      //et également ici :
      private

void Exec_ThreadDiaporama()
      {

         IAsyncResult ias =
this.BeginInvoke(
new
delegateDiaporama(Exec_Diapo_avecEffetAlpha));

         if (CancelKeyPressed)
         {

            if (
MessageBox.Show(
"Arrêter le diaporama ?",
"Demande d'arrêt du diaporama",

            MessageBoxButtons.YesNo,
MessageBoxIcon.Question) ==
DialogResult.Yes)
            {
               CancelKeyPressed =
false;
               ThreadDiaporama.Abort();
            }
         }

         this.EndInvoke(ias);
      }
      (...)
   }
}




En fait j'override ProcessCmdKey suite à un post qui expliquait comment et pourquoi on pouvait le faire ... malheureusement je ne me souviens plus du lien vers ce post ...

Mais avant de t'embêter à me répondre, laisse-moi le temps de suivre tes précieux conseils :




je vais de suite m'inscrire à l'évènement keypressed de ma form (...) , mettre keypreview à true, ainsi que isbackground ...

Je te tiens au courant dès que j'aurai fait mes tests

Merci encore.






 
Commenter la réponse de avatar1108
avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 16 janv. 2008 à 23:50
0
Merci
Salut,




J'ai suivi tous tes conseils :




1) J'ai mis la propriété KeyPreview de mon Form à true.




2) J'ai mis la propriété IsBackground de mon thread à true, ici :









private



void
btnExecDiapo_Click(

object
sender,

EventArgs
e)
{
   (...)   
   ThreadDiaporama =
new
Thread(
new
ThreadStart(Exec_ThreadDiaporama));
   ThreadDiaporama.IsBackground =
true;
   ThreadDiaporama.Start();
}

3) Je détecte correctement la touche Escape (je l'ai testé avec une messagebox et ça marche), avec ce ' if ', comme ça :

if
(e.KeyCode ==
Keys.Escape)

4) Et comme tu me l'as dit, je vérifie aussi si mon thread n'est pas null et si la propriété IsAlive de ce même thread est bien à true, ce qui donne en tout :

private
void formPleinEcran_KeyDown(
object sender,
KeyEventArgs e)
{

   if (ThreadDiaporama !=
null && ThreadDiaporama.IsAlive ==
true)
   {
      
if (e.KeyCode ==
Keys.Escape)
      {
         ThreadDiaporama.Abort();
      }
   }
}J'ai du faire une erreur quelque part (je te rappelle que je ne suis qu'un débutant), car je n'arrive toujours pas à arrêter mon ThreadDiaporama pendant son exécution en appuyant sur la touche Escape !

Par ailleurs, il y a quelque chose qui me travaille, c'est peut-être une piste à suivre. Pourquoi ne pas faire le test de la touche escape entre le
BeginInvoke et le EndInvoke puisque tu disais dans ton tuto que c'est l'endroit ou les instructions peuvent être exécutées sans attendre, le problème c'est que je ne sais pas vraiment comment le faire. Ce test, dans mon cas, se ferait dans le code ci-après :

private

Thread ThreadDiaporama;

private
delegate
void
delegateDiaporama();

// code qui démarre mon thread secondaire que j'appelle 'ThreadDiaporama' par un click sur le bouton 'btnExecDiapo'
private
void btnExecDiapo_Click(
object sender,
EventArgs e)
{
   ThreadDiaporama =
new
Thread(
new
ThreadStart(Exec_ThreadDiaporama));
   ThreadDiaporama.IsBackground =
true;
   ThreadDiaporama.Start();
}

private
void Exec_ThreadDiaporama()
{

   IAsyncResult ias =
this.BeginInvoke(
new
delegateDiaporama(ExecDiapoAvecEffetAlpha));
   // c'est ici que je mettrais le test pour détecter la frappe de la touche Escape.
   // si la touche Escape est détectée, je fais mon ' ThreadDiaporama.Abort(); '.
   this
.EndInvoke(ias);
}

private

void ExecDiapoAvecEffetAlpha()
{
   // code qui fait apparaitre et disparaitre, une à une, les images d'une listview, à l'aide d'une boucle, avec un effet alpha (en utilisant la classe ColorMatrix)
}
C'est quand même dommage j'ai appris grace à toi et à ce site, à démarrer un thread, mais je ne sais même pas comment l'arrêter si besoin est !

Au fait, j'ai retrouvé le lien qui montre comment redéfinir ProcessCmdKey, c'était une excellente solution apportée par Lutinore :

http://www.csharpfr.com/infomsg_INTERCEPTER-TOUCHES-CLAVIER_724610.aspx



C'est avec cette solution que je pensais pouvoir faire le test de la touche Escape entre le BeginInvoke et le EndInvoke.
Mais, je ne sais pas comment m'y prendre. Peut-être me suis-je mis sur une fausse piste !
Je commence à désespérer, j'ai l'impression de marcher sur des sables mouvants !
Pourtant, je suis sûr que quelqu'un connait la solution.

J'ai encore besoin d'un petit coup de pouce svp.

Avatar.
 
Commenter la réponse de avatar1108
avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 17 janv. 2008 à 22:17
0
Merci
Salut Mx,
Ne sois pas désolé, je comprends que tu puisses être très demandé ...
Je m'excuse de ne pas t'avoir répondu plus tôt ...
Merci de t'être repenché sur mon problême.
Je vais de suite étudier tes exemples de code, notamment l'exemple 2 (je suis encore pas prêt de me coucher moi !),
pour essayer de te dire rapidement si j'y arrive ...
j'espère vite pouvoir accepter ta réponse, car franchement, tu le mérites.

Avatar.
Commenter la réponse de avatar1108
avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 18 janv. 2008 à 19:03
0
Merci
Salut Mx,

im-pe-ccable, for-mi-dable !!!
Ta 2ème solution m'a vite mis sur la bonne voie.
Je n'ai pas eu de mal à tester ton exemple 2.
En revanche, pour le mettre en pratique dans mon appli, c'était une autre paire de manches !
Voici ce que j'ai fait en condensé :
privatestring _PathName;<?xml:namespace prefix o ns "urn:schemas-microsoft-com:office:office" /??>

privatestring _strSelActuFileName = "";

privateThread ThreadDiaporama;

privatedelegatevoidDelLvw (int i);

privatedelegatevoidDelImgApparait();

privatedelegatevoidDelImgDisparait();

privateint iTempsVisu;

 

privatevolatilebool bStopThread;

 

public form1()

{

    InitializeComponent();

 

    this.KeyPreview = true;

    this.KeyDown += newKeyEventHandler(form1_KeyDown);

}

 

privatevoid btnExecDiapo_Click(object sender, EventArgs e)

{

    bStopThread = false;

 

    if (ThreadDiaporama == null)

    {

        ThreadDiaporama = newThread(newThreadStart(Exec_ThreadDiaporama));

        ThreadDiaporama.IsBackground = true;

        ThreadDiaporama.Start();

    }

}

 

privatevoid form1_KeyDown(object sender, KeyEventArgs e)

{

    if (ThreadDiaporama != null && ThreadDiaporama.IsAlive)

    {

        if (e.KeyCode == Keys.Escape)

        {

            bStopThread = true;

            //ThreadDiaporama.Join();

            ThreadDiaporama = null;

        }

    }

}

 

privatevoid Exec_ThreadDiaporama()

{

    int iSelActu = 0;

    do

    {

        IAsyncResult ias1 = this.BeginInvoke(newDelLvw(Exec_lvw), iSelActu);

        IAsyncResult ias2 = this.BeginInvoke(newDelImgApparait(Exec_ImgApparait));

        Thread.Sleep(iTempsVisu);

        IAsyncResult ias3 = this.BeginInvoke(newDelImgDisparait(Exec_ImgDisparait));

        this.EndInvoke(ias1);

        this.EndInvoke(ias2);

        this.EndInvoke(ias3);

        iSelActu++;

    } while (!bStopThread && iSelActu < lvwImageList2.Items.Count);

    ThreadDiaporama = null;

}

 

privatevoid Exec_lvw(int i)

{

    _strSelActuFileName = this.lvwImageList2.Items[i].Text;

    pictboxPE.Image = Image.FromFile(_PathName + _strSelActuFileName);

}

 

privatevoid Exec_ImgApparait()

{

    (…)

}

 

privatevoid Exec_ImgDisparait()

{

    (…)

}

Comme on peut le voir, j'ai choisi de ne plus utiliser le ThreadDiaporama.Abort() , même si cela marchait avec. Pour la simple raison qu'il parait que c'est loin d'être recommandé, il suffit de faire une recherche sur google en soumettant par exemple "thread.abort is evil" et on tombe sur des liens qui explique pourquoi.
Mais surtout, je me suis inspiré aussi d'un article plutôt intéressant de la MSDN dont voici le lien :
http://msdn2.microsoft.com/fr-fr/library/7a2f3ay4(VS.80).aspx
Cela m'a permis d'utiliser un booléen que j'ai déclaré avec le mot-clé volatile qui permet de spécifier au compilateur que plusieurs thread pourront y accéder. Je me suis dit que cela me serait peut-être plus utile qu'un booléen plus "classique"... C'est ce booléen que je mets à true si la touche escape est appuyée, ce qui me permet d'arrêter ma boucle aussi facilement qu'avec un thread.Abort() , mais plus proprement aussi, d'après ce que j'ai compris.
D'après l'article de la MSDN, il fallait également utiliser thread.Join() , ce que j'ai essayé de faire, mais cela me bloquait l'appli !
Peut-être est-ce à cause du fait que je fais un ThreadDiaporama = null tout de suite après, mais j'étais obligé de le faire car dans ton exemple2, tu executes 3 lignes (dont le thread.start() ) seulement si le thread est bien nul.
J'ai donc une petite question (si tu as le temps biensur !) :

Pourquoi tu exécutes ces 3 lignes à condition que le thread soit nul ??

En effet, dans mes premiers tests, j'ai remarqué que je pouvais executer le diaporama une première fois en cliquant sur le bouton, mais pas les fois suivantes ! tout simplement parce-que le thread, à cette deuxième exécution n'était plus null, c'est pourquoi si je veux respecter ton code et garder ce if, je dois faire un ThreadDiaporama = null , deux fois dans le mien !

Autre chose : connaitrais-tu un moyen, histoire de ne pas risquer d'avoir des soucis et de faire plus propre, d'utiliser thread.Join() dans mon cas ? Ou peut-être sais-tu que cela n'est pas utile dans ce contexte ?

En tous cas, ça marche super ! et je te remercie infiniment, car ce problème me bloquait depuis un sacré temps.
Si je peux donner mon avis, je dirais que les Admin CS sont un genre de héros méconnus !!!

Je m'empresse d' accepter ta réponse.

Avatar.
Commenter la réponse de avatar1108
avatar1108 6 Messages postés samedi 16 juin 2007Date d'inscription 19 janvier 2008 Dernière intervention - 19 janv. 2008 à 17:55
0
Merci
Bonjour,

J'ai finalement décidé d'utiliser le thread.Abort() plutôt que le booléen volatile, on va finir par croire que je ne sais pas ce que je veux mais j'ai pensé qu'il était utile de le signaler, car un booléen est loin d'être capable, d'après ma petite expérience et dans le contexte de mon code, d'arrêter un thread aussi rapidement que la méthode Abort.

En effet, même s'il parait que c'est plus propre avec un booléen, il faut attendre la fin de l'itération pour qu'il soit évalué; par conséquent, si j'ai un thread.Sleep(iTempsVisu) dans le cas où iTempsVisu vaut 20000 dans la boucle, il faut patienter 20 secondes jusqu'à ce que celle-ci se termine et que le thread de mon diaporama s'arrête !

Peut-être que quelque chose m'a échappé mais si quelqu'un connait un moyen propre pour arrêter un thread sans utiliser cette méthode Abort qui n'est pas recommandé (aux dires de microsoft ! si j'ai bien compris), je suis preneur.

Merci, à +.

Avatar.
Commenter la réponse de avatar1108

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.