FAQ C++/CLI et VC++.Net
FAQ C++/CLI et VC++.NetConsultez toutes les FAQ
Nombre d'auteurs : 29, nombre de questions : 248, création le 22 février 2013
- Comment créer et lancer un thread ?
- Comment passer un ou plusieurs paramètres à un thread ?
- Comment arrêter un thread ?
- Comment changer le nom du thread courant ?
- Comment ne lancer qu'une seule instance de mon application ?
- Comment lancer un processus ?
- Comment ouvrir un fichier avec l'application associée à son extension ?
- Comment rediriger la sortie standard d'un processus ?
- Comment lister les processus en cours d'exécution ?
- Comment arrêter un processus ?
Pour créer un thread, il faut utiliser la classe System::Threading::Thread.
Considérons que l'on dispose d'une Form.
Nous avons besoin de déclarer notre objet thread à l'intérieur de la Form.
using
namespace
System::
Threading;
public
ref class
Threads : public
System::Windows::Forms::
Form
{
// ........
private
: Thread ^
_threadCalculs1;
}
La fonction exécutée par le thread a une signature imposée par le .NET Framework. Il s'agit du délégué System::Threading::ThreadStart.
C'est une fonction qui ne prend aucun paramètre et ne possède pas de valeur de retour.
On peut la déclarer ainsi dans notre Form.
private
:void
ThrFunc1()
{
// Traitement effectué par le thread. Calculs est une fonction quelconque de notre Form
try
{
Calculs(1000
) ;
}
catch
(Exception ^
ex)
{
System::Diagnostics::Debug::
WriteLine(ex->
ToString());
}
}
Pour démarrer le thread, on utilise la fonction Start de la classe Thread.
private
: void
StartThread()
{
// ThrFunc1 est la fonction exécutée par le thread.
_threadCalculs1 =
gcnew Thread(gcnew ThreadStart(this
, &
Threads::
ThrFunc1));
// Il est parfois pratique de nommer les threads surtout si on en créé plusieurs.
_threadCalculs1->
Name =
"Thread1"
;
// Démarrage du thread.
_threadCalculs1->
Start();
}
Le délégué System::Threading::ThreadStart utilisé pour les fonctions de thread ne prend pas de paramètres.
Pour passer des paramètres à un thread, il vous faut créer une classe pour contenir les paramètres et la méthode du thread.
using
namespace
System;
using
namespace
System::
Threading;
ref class
ThreadParametre
{
private
:
String ^
_text;
int
_entier;
public
:
// Constructeur
ThreadParametre(String ^
texte, int
entier)
{
_text =
texte;
_entier =
entier;
}
// Exécution de la méthode du thread
void
ExecuteThread()
{
for
(int
i =
0
; i <
_entier; i++
)
{
Console::
WriteLine("Index : "
+
i);
Console::
WriteLine("Message : "
+
_text);
}
}
}
;
int
main(array<
System::
String ^>
^
args)
{
ThreadParametre ^
ExempleThread =
gcnew ThreadParametre("Message de test"
, 5
);
Thread ^
t =
gcnew Thread(gcnew ThreadStart(ExempleThread, &
ThreadParametre::
ExecuteThread));
t->
Start();
return
0
;
}
Le meilleur moyen de d'arrêter un thread est de laisser sa fonction se terminer.
Si une fonction de thread s'exécute en continu dans une boucle, il est nécessaire d'écrire un code qui prévoit une condition pour sortir de la boucle. Cette condition doit pouvoir être modifiée par d'autres threads.
Reprenons l'exemple de notre Form (voir Q/R création d'un thread).
Pour signaler au thread que nous souhaitons qu'il s'arrête, nous allons utiliser un objet de la classe System::Threading::AutoResetEvent.
Dans la boucle de la fonction du thread, nous faisons attendre le thread pendant un court laps de temps. Si l'objet AutoResetEvent passe à l'état signalé, alors on sort du thread.
using
namespace
System;
using
namespace
System::
Threading;
public
ref class
Threads : public
System::Windows::Forms::
Form
{
private
: Thread ^
_threadCalculs1;
// Evènement de signal de fin de thread
private
: AutoResetEvent ^
_endThreadCalculsEvent;
public
:
Threads(void
)
{
InitializeComponent();
_endThreadCalculsEvent =
gcnew AutoResetEvent(false
);
}
private
:void
ThrFunc1()
{
// Traitement effectué par le thread. Calculs est une fonction quelconque de notre Form
try
{
Calculs(1000
) ;
}
catch
(Exception ^
ex)
{
System::Diagnostics::Debug::
WriteLine(ex->
ToString());
}
}
private
: void
StartThread()
{
// ThrFunc est la fonction exécutée par le thread.
_threadCalculs1 =
gcnew Thread(gcnew ThreadStart(this
, &
Threads::
ThrFunc1));
// Il est parfois pratique de nommer les threads surtout si on en créé plusieurs.
_threadCalculs1->
Name =
"Thread1"
;
// Démarrage du thread.
_threadCalculs1->
Start();
}
void
Calculs(int
tempo)
{
// Si l'événement est à l'état signalé, WaitOne renvoie true et la boucle se termine.
while
(!
_endThreadCalculsEvent->
WaitOne(tempo, false
) )
{
// C'est ici ou notre thread fait son travail
// .....
}
}
// pour arreter le thread
private
:
System::
Void button1_Click_1(System::
Object^
sender, System::
EventArgs^
e)
{
// L'événement passe à l'état signalé
_endThreadCalculsEvent->
Set();
// On attend la fin du thread.
_threadCalculs1->
Join();
}
}
;
Il existe un moyen plus radical d'arrêter un thread, c'est l'utilisation de la fonction Thread::Abort.
Lorsque vous appelez Abort, le Runtime lève une exception ThreadAbortException que le thread peut alors intercepter.
C'est aussi pourquoi il est déconseillé d'utiliser Abort car on ne peut prévoir où en est le thread dans son traitement. Lever une exception peut interrompre le thread alors qu'il est dans une partie du code qu'il doit terminer avant de sortir.
Un des exemples où on peut utiliser Abort sans risque : la fonction du thread est bloquée infiniment sur un appel (une attente de connexion socket par exemple).
// Forcer la fin du thread
void
AbortThread()
{
_threadCalculs1->
Abort(); // On demande au runtime d'arrêter le Thread
_threadCalculs1->
Join(); // On attend la fin du thread.
}
Un thread terminé ne peut plus être relancé. Il faut instancier un nouvel objet Thread pour chaque démarrage de thread.
Tout d'abord pensez à la clause:
using
namespace
System::
Threading;
Pour changer le nom du thread courant, ajoutez la ligne de code suivante :
Thread::
CurrentThread->
Name =
"MainThread"
;
Ici on donne le nom "MainThread" au thread courant.
Si vous devez réaliser cela pour le thread principal, alors il faut ajouter cette ligne avant le "Application::Run(...)".
Il arrive souvent de souhaiter interdire à une application d'avoir plusieurs instances lancées.
Voici une petite classe qui lors du démarrage de l'application, s'assure qu'elle n'est pas déjà en cours d'exécution.
Elle utilise un objet mutex nommé, donc potentiellement visible par tous les autres processus.
ref class
SingleInstanceApp
{
private
:
System::Threading::
Mutex ^
_siMutex;
bool
_siMutexOwned;
public
:
SingleInstanceApp(String ^
name)
{
_siMutex =
gcnew System::Threading::
Mutex(false
, name);
_siMutexOwned =
false
;
}
~
SingleInstanceApp()
{
// Libération du mutex si il a été acquis
if
(_siMutexOwned)
_siMutex->
ReleaseMutex();
}
// Application déjà lancée ?
bool
IsRunning()
{
// Acquisition du mutex.
// Si _siMutexOwned vaut true, l'application acquiert le mutex car il est "libre"
// Sinon le mutex a déjà été acquis lors du lancement d'une instance précédente
_siMutexOwned =
_siMutex->
WaitOne(0
, true
);
return
!
(_siMutexOwned);
}
}
;
Pour utiliser notre classe, il suffit de procéder ainsi dans le Main de notre application.
SingleInstanceApp ^
app =
gcnew SingleInstanceApp("{123456789 - ABCD - EFEG - XXXX}"
);
if
(app->
IsRunning())
MessageBox::
Show("Application déjà lancée"
);
else
Application::
Run(gcnew Form1());
delete
app;
Important :
Si une application lambda en cours d'exécution crée un mutex ayant le même nom que celui de notre application, cette dernière ne pourra plus se lancer.
Elle se comportera comme si une autre instance de l'application était déjà en cours.
Il existe une technique pour l'éviter mais cela sort de notre sujet. Veuillez donc à choisir un nom assez compliqué pour votre mutex.
Remarque : On appelle explicitement le destructeur avec delete car on ne peut pas libérer le mutex dans le finalizer.
Pour lancer un processus depuis notre application, on utilise la classe System::Diagnostics::Process.
Exemple : lancer une instance de internet explorer qui ouvre www.developpez.com
void
StartProcess()
{
// Instance de la classe Process
System::Diagnostics::
Process ^
proc =
gcnew System::Diagnostics::
Process();
// Nom de l'exécutable à lancer
proc->
StartInfo->
FileName =
"iexplore.exe"
;
// Arguments à passer à l'exécutable à lancer
proc->
StartInfo->
Arguments=
"http://www.developpez.com"
;
// Démarrage du processus
proc->
Start() ;
// On libère les ressources dont on n'a plus besoin.
proc->
Close(); // Attention Close ne met pas fin au processus.
}
On peut ouvrir des documents dont l'extension est connue du shell windows comme les .txt ou les .doc avec la classe System::Diagnostics::Process.
Exemple : Ouverture d'un fichier texte .txt.
//Instance de la classe System.Diagnostics.Process
System::Diagnostics::
Process ^
proc =
gcnew System::Diagnostics::
Process();
//Nom du fichier dont l'extension est connue du shell à ouvrir
proc->
StartInfo->
FileName =
"monfichier.txt"
;
//Démarrage du processus. Notepad (si il est associé aux fichiers .txt) sera alors lancé et ouvrira le fichier monfichier.txt
proc->
Start();
//On libère les ressources
proc->
Close();
Il est possible de rediriger la sortie standard d'un processus et de l'afficher dans un TextBox multiligne par exemple.
String ^
RedirectStdOutput(String ^
nomProcess)
{
System::Diagnostics::
Process ^
proc =
gcnew System::Diagnostics::
Process();
// On désactive le shell
proc->
StartInfo->
UseShellExecute =
false
;
// On redirige la sortie standard
proc->
StartInfo->
RedirectStandardOutput =
true
;
// On définit la commande
proc->
StartInfo->
FileName =
nomProcess;
// Démarrage de la commande
proc->
Start();
// Attente de la fin de la commande
proc->
WaitForExit();
// Lecture de la sortie de la commande
String ^
output =
proc->
StandardOutput->
ReadToEnd();
// Libération des ressources
proc->
Close();
return
output;
}
Pour lister les processus en cours on utilise la fonction :
// Pour tous les processus en cours sur l'ordinateur local
array<
System::Diagnostics::
Process ^>
^
prc =
Process::
GetProcesses();
// Pour tous les processus notepad en cours sur l'ordinateur local
array<
System::Diagnostics::
Process ^>
^
prc =
Process::
GetProcessesByName("notepad"
);
Pour arrêter un processus, il faut disposer d'un objet System::Diagnostics::Process qui représente le processus.
// Pour les applications consoles
proc->
Kill();
// Libération des ressources
proc->
Close();
Pour les applications WinForm il est préférable d'utiliser CloseMainWindow afin que l'application reçoive le message de fermeture et se ferme correctement.
// Pour les applications avec une interface graphique (et donc une pompe de messages)
// Si l'appel échoue, on peut alors forcer la fermeture avec Kill.
proc->
CloseMainWindow();
// Libération des ressources
proc->
Close();