Problème multithreading pourtant simple sous Visual C++ & MFC

cs_kelly Messages postés 55 Date d'inscription jeudi 1 mai 2003 Statut Membre Dernière intervention 9 septembre 2004 - 1 sept. 2004 à 01:40
ymca2003 Messages postés 2070 Date d'inscription mardi 22 avril 2003 Statut Membre Dernière intervention 3 juillet 2006 - 2 sept. 2004 à 22:42
Au secours !
Je m'arrache les cheveux depuis des jours en essayant de comprendre d'ou vient le probleme :

Mon application dispose de boutons type magnétophone : Play, Pause, Stop.

Un clic sur PLAY lance un thread, jusque là aucun probleme.

Un clic sur STOP devrait signaler au thread précédemment lancé qu'il est temps pour celui-ci de se terminer.
Pour ce faire, la fonction exécutée par le clic sur STOP change l'état d'une variable booleenne globale : m_PleaseStop=true.

La thread PLAY vérifie à chaque tour de boucle (c'est un algo qui tourne en boucle) la valeur de m_PleaseStop. Quand m_PleaseStop==true, la fonction de la thread PLAY se dirige vers la sortie et juste avant de finir, réaffecte m_PleaseStop=false, pour la forme.

Bref, mon problème est que mon programme se bloque de manière aléatoire quand PLAY est en route et que je clique sur STOP (la boucle PLAY s'arrete effectivement puis je n'ai plus la main, la thread étant toujours existante).

J'ai remarqué qu'en éliminant une ligne UpdataData(false) présente dans la boucle PLAY, le problème disparait et tout fonctionne normalement, la thread PLAY se ferme et tout va bien.

J'ai alors essayé de mettre des sections critiques, des temporisations etc. mais rien n'y fait, et quoi de plus normal puisque la procédure STOP n'utilise aucun objet spécial.

Je ne comprends pas, aidez moi SVPPPPPPPPPP !

9 réponses

cs_AlexMAN Messages postés 1536 Date d'inscription samedi 21 décembre 2002 Statut Membre Dernière intervention 24 mai 2009 1
1 sept. 2004 à 01:56
Je sais pas si ca pourrait marcher, mais essaye, sait on jamais :

#define WM_ENDTHRPLAY WM_USER + 1

int Stop (...)
{
...
PostMessage(hwnd, WM_ENDTHRPLAY, 0, 0);
...
}

LRESULT CALLBACK WndProc(...)
{
...
case WM_ENDTHRPLAY:
TerminateThread(hthreadplay);
CloseHandle(hthreadplay);
return 0;
...
}

Vraiment po sur de la fonctionnalité du code ci dessus, teste et envoie nous une réponse ;)

Allez, bonne nuit a toi

++
0
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
1 sept. 2004 à 11:07
AlexMAN, c'est du MFC son affaire. Jamais de TerminateThread(), c'est une fonction d'extreme urgence qui engendrerait des enormes fuites memoire. Son thread doit finir proprement.

ciao...
BruNews, Admin CS, MVP VC++
0
cs_AlexMAN Messages postés 1536 Date d'inscription samedi 21 décembre 2002 Statut Membre Dernière intervention 24 mai 2009 1
1 sept. 2004 à 11:16
Je sais que TerminateThread ne doit pas etre utilisé (libere pas les ressources...etc), faut il utiliser ExitThread a la place ?
0
cs_AlexMAN Messages postés 1536 Date d'inscription samedi 21 décembre 2002 Statut Membre Dernière intervention 24 mai 2009 1
1 sept. 2004 à 11:17
Et dsl, j'avais pas vu que c'etait du MFC, dsl.
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
cs_kelly Messages postés 55 Date d'inscription jeudi 1 mai 2003 Statut Membre Dernière intervention 9 septembre 2004
1 sept. 2004 à 22:51
La méthode d'Alexman doit en effet fonctionner et je l'en remercie, mais c'est vrai que si je trouvais le moyen de terminer la thread proprement (par sa propre volonté) , ca m'arrangerait...

Je répète cependant mon observation sur le comportement de mon code : si je supprime les appels UpdateData(false) réalisés par la thread PLAY, tout fonctionne comme sur des roulettes...

Mes threads freezent sur des UpdateData(false) quoi... et il n'y a meme pas de section critique ou autre à mettre en oeuvre puisque STOP ne demande rien au systeme ! pourquoi ? pourquoi moi ??
0
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
1 sept. 2004 à 22:57
Tu ne serais pas en MFC j'y aurais jete un oeil mais la desole...
Faut bien remonter toutes les actions engendrees par UpdateData(false) dans ton code, y a que ça a faire.

ciao...
BruNews, Admin CS, MVP VC++
0
ymca2003 Messages postés 2070 Date d'inscription mardi 22 avril 2003 Statut Membre Dernière intervention 3 juillet 2006 7
2 sept. 2004 à 12:32
je suppose que tu passe en param de ton thread un pointeur sur le CDialog et que tu utilise ce pointeur pour faire UpdateData.

voici ce qui est dit dans les sources MFC :

"wincore.cpp" ligne 886
void CWnd::AssertValid() const

// Note: if either of the above asserts fire and you are
// writing a multithreaded application, it is likely that
// you have passed a C++ object from one thread to another
// and have used that object in a way that was not intended.
// (only simple inline wrapper functions should be used)
//
// In general, CWnd objects should be passed by HWND from
// one thread to another. The receiving thread can wrap
// the HWND with a CWnd object by using CWnd::FromHandle.
//
// It is dangerous to pass C++ objects from one thread to
// another, unless the objects are designed to be used in
// such a manner.

en gros, dans le thread il faut éviter d'utiliser des fonctions spéciales MFC et n'utiliser que les fonctions API déguisées (implémentée en inline avec le HWND implicite).
0
cs_kelly Messages postés 55 Date d'inscription jeudi 1 mai 2003 Statut Membre Dernière intervention 9 septembre 2004
2 sept. 2004 à 18:50
Merci quand meme Brunews!
-> ymca2003 : En effet, c'est très exactement la manipulation que j'ai faite. D'ailleurs si je compile en debug, une librairie interne responsable de UpdateData crie (assertion invalide) que la classe appelante n'est pas la meme que le CDialog dont je veux rafraichir le contenu des controles standards (je détaille au cas ou qqun d'autre est intéressé par le probleme). Ce qui est bizarre puisque l'appel se présente comme suit :

Appel lors du clic sur PLAY :
void CMonAppDlg::OnBRestart() 
{
  ...
  AfxBeginThread( g_PseudoThreadFunction, (LPVOID)this ) ;
}


Pseudo fonction thread définie en global :
UINT g_PseudoThreadFunction(LPVOID pParam)
{
  ((CMonAppDlg*)pParam) -> m_ThreadFunc() ;
  return 0 ;
}


Fonction thread effectivement appelée :
void CMonAppDlg::m_ThreadFunc()
{
  ...
  UpdateData(false) ;
  ...
  return ; // ferme proprement le thread en remontant les appels
}


D'ou vient alors cette assertion invalide ?
Pourquoi en compilant en release, tout passe bien, et le programme ne freeze d'ailleurs "que" 9 fois sur 10 ?
Une chose est sure, le freeze venait d'une routine au plus profond de l'OS.
Merci de me faire encore profiter de tes lumieres !
0
ymca2003 Messages postés 2070 Date d'inscription mardi 22 avril 2003 Statut Membre Dernière intervention 3 juillet 2006 7
2 sept. 2004 à 22:42
pour t'en sortir, il suffit d'associer chacun des contrôles dont tu as besoin de la boîte de dialogues à leur classe respectives (et pas int , CString ou autre variables).

ensuite, au lieu d'appeler UpdateData, tu te sert de ton pointeur sur le dialogue pour récuper les controles et faire doit même les mises à jour (UpdateData est censé simplifié le travail en associant un CString a un Edit par exemple, mais il est aussi simple d'affecter soi-même le nouveau texte à l'Edit, ex (chrono):

//***************************************************************************************
// MFCThreadDlg.h : header file
//
//***************************************************************************************

#ifndef AFX_MFCTHREADDLG_H_INCLUDED_
#define AFX_MFCTHREADDLG_H_INCLUDED_

//***************************************************************************************
// Classe CMFCThreadDlg.
//***************************************************************************************
class CMFCThreadDlg : public CDialog
{
//=======================================================================================
// Membres privés.
//=======================================================================================
private :

// icone associée à la boîte de dialogue
HICON	m_hIcon;

// police de caractère pour l'affichage
CFont*	m_pFont;

// heure de départ du chronomètre, offset éventuel à rajouter
DWORD	m_dwStartTime;
DWORD	m_dwOffsetTime;

// thread du chronomètre, états (m_bThreadRunning est volatile pour que sa valeur
// soit évaluée à chaque boucle et éviter des optimisations du compilateur menant
// à un deadlock)
CWinThread*		m_pThread;
BOOL			m_bPause;
volatile BOOL	m_bThreadRunning;

// synchronisation des threads (pour qu'un seul ai accès aux variables partagées
// à un instant donné)
CRITICAL_SECTION	m_cs;

//=======================================================================================
// Méthodes privées.
//=======================================================================================
private :
// met à jour l'état des boutons
void UpdateButtons();

// point d'entrée du thread du chronomètre
static UINT ThreadFunc(LPVOID lpData);

//=======================================================================================
// Méthodes publiques.
//=======================================================================================
public :
// constructeur
CMFCThreadDlg(CWnd* pParentWnd = NULL);

//=======================================================================================
// Données de la boîte de dialogue.
//=======================================================================================
protected :
//{{AFX_DATA(CMFCThreadDlg)
enum { IDD = IDD_MFCTHREAD };
CStatic	m_StcCounter;
CButton	m_BtnStart;
CButton	m_BtnStop;
CButton	m_BtnPause;
//}}AFX_DATA

//=======================================================================================
// Méthodes virtuelles redéfinies.
//=======================================================================================
//{{AFX_VIRTUAL(CMFCThreadDlg)
protected :
virtual void DoDataExchange(CDataExchange* pDX);
//}}AFX_VIRTUAL

//=======================================================================================
// Traitement des messages.
//=======================================================================================
protected :
//{{AFX_MSG(CMFCThreadDlg)
virtual BOOL OnInitDialog();
afx_msg void OnBtnStart();
afx_msg void OnBtnStop();
afx_msg void OnBtnPause();
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

#endif	// AFX_MFCTHREADDLG_H_INCLUDED_


//***************************************************************************************
// MFCThreadDlg.cpp : implementation file
//
//***************************************************************************************

#include "StdAfx.h"
#include "MFCThread.h"

#include "MFCThreadDlg.h"

//=======================================================================================
// Tables des messages pour CMFCThreadDlg.
//=======================================================================================
BEGIN_MESSAGE_MAP(CMFCThreadDlg, CDialog)
//{{AFX_MSG_MAP(CMFCThreadDlg)
ON_BN_CLICKED(IDC_MFCTHREAD_BTN_START, OnBtnStart)
ON_BN_CLICKED(IDC_MFCTHREAD_BTN_STOP, OnBtnStop)
ON_BN_CLICKED(IDC_MFCTHREAD_BTN_PAUSE, OnBtnPause)
ON_WM_DESTROY()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//***************************************************************************************
// Constructeur.
//***************************************************************************************
CMFCThreadDlg::CMFCThreadDlg(CWnd* pParentWnd)
: CDialog(CMFCThreadDlg::IDD, pParentWnd)
{
//{{AFX_DATA_INIT(CMFCThreadDlg)
//}}AFX_DATA_INIT

// initialisation des paramètres
m_pFont				= NULL;
m_dwStartTime		= 0;
m_dwOffsetTime		= 0;
m_pThread			= NULL;
m_bThreadRunning	= FALSE;
m_bPause			= FALSE;

// chargement de l'icone asociée à la boîte de dialogue
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

//***************************************************************************************
// DoDataExchange :
//***************************************************************************************
void CMFCThreadDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CMFCThreadDlg)
DDX_Control(pDX, IDC_MFCTHREAD_STC_COUNTER, m_StcCounter);
DDX_Control(pDX, IDC_MFCTHREAD_BTN_START, m_BtnStart);
DDX_Control(pDX, IDC_MFCTHREAD_BTN_STOP, m_BtnStop);
DDX_Control(pDX, IDC_MFCTHREAD_BTN_PAUSE, m_BtnPause);
//}}AFX_DATA_MAP
}

//***************************************************************************************
// UpdateButtons :
//***************************************************************************************
void CMFCThreadDlg::UpdateButtons()
{
// bouton [Démarrer]
m_BtnStart.EnableWindow(m_pThread == NULL);

// bouton [Arrêter]
m_BtnStop.EnableWindow(m_pThread != NULL);

// bouton [Pause\Reprendre]
m_BtnPause.SetWindowText(m_bPause ? _T("Reprendre") : _T("Pause"));
m_BtnPause.EnableWindow(m_pThread != NULL);
}

//***************************************************************************************
// ThreadFunc :
//***************************************************************************************
UINT CMFCThreadDlg::ThreadFunc(LPVOID lpData)
{
// récupération objet CMFCThreadDlg associé
CMFCThreadDlg* pMFCThreadDlg = (CMFCThreadDlg*)lpData;

// tant que le thread doit tourner (m_bThreadRunning est volatile pour que sa valeur
// soit évaluée à chaque boucle et éviter des optimisations du compilateur menant
// à un deadlock. A priori inutile si utilisée à travers un pointeur)
while(pMFCThreadDlg->m_bThreadRunning)
{
// pause de 5 ms
Sleep(5);

// calcul de la nouvelle valeur du chronomètre
EnterCriticalSection(&pMFCThreadDlg->m_cs);
DWORD dwTime = GetTickCount()-pMFCThreadDlg->m_dwStartTime+
pMFCThreadDlg->m_dwOffsetTime;
LeaveCriticalSection(&pMFCThreadDlg->m_cs);

// décomposition des minutes, secondes et centièmes de seconde
int nMinutes = dwTime/60000;
int nSeconds = (dwTime-60000*nMinutes)/1000;
int nHundreth = (dwTime%1000)/10;

// formatage chaîne et affichage (sauf si le thread a été arrêté entre temps)
if(pMFCThreadDlg->m_bThreadRunning)
{
CString strTime;
strTime.Format(_T("%02d:%02d:%02d"), nMinutes, nSeconds, nHundreth);
pMFCThreadDlg->m_StcCounter.SetWindowText(strTime);
}
}
return 0;
}

//=======================================================================================
// Gestionnaire de messages pour CMFCThreadDlg.
//=======================================================================================

//***************************************************************************************
// OnInitDialog :
//***************************************************************************************
BOOL CMFCThreadDlg::OnInitDialog()
{
// appel fonction de la classe de base
CDialog::OnInitDialog();

// affectation des icones (grande et petite)
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);

// création police de caractères (3/4 de la hauteur du CStatic)
CRect rcStcCounter;
m_StcCounter.GetClientRect(&rcStcCounter);
m_pFont = new CFont();
m_pFont->CreateFont(3*rcStcCounter.Height()/4, 0, 0, 0, FW_NORMAL, FALSE, FALSE,
FALSE, ANSI_CHARSET, 0, 0, 0, 0, NULL);

// affectation au contrôle CStatic
m_StcCounter.SetFont(m_pFont, FALSE);

// initialisation section critique
InitializeCriticalSection(&m_cs);

// mise à jour de l'état des boutons
UpdateButtons();

// retour
return TRUE;
}

//***************************************************************************************
// OnDestroy :
//***************************************************************************************
void CMFCThreadDlg::OnDestroy()
{
// destruction de la police de caractères
ASSERT(m_pFont != NULL);
m_pFont->DeleteObject();
delete m_pFont;

// on stoppe le chronomètre
OnBtnStop();

// destruction section critique
DeleteCriticalSection(&m_cs);

// appel fonction de la classe de base
CDialog::OnDestroy();
}

//***************************************************************************************
// OnBtnStart :
//***************************************************************************************
void CMFCThreadDlg::OnBtnStart()
{
// si un thread est déjà en cours
if(m_pThread != NULL)
return;

// heure de début, offset
m_dwStartTime = GetTickCount();
m_dwOffsetTime = 0;

// création d'un nouveau thread (on met m_bAutoDelete car on gèrera la destruction)
m_bPause = FALSE;
m_bThreadRunning = TRUE;
m_pThread = AfxBeginThread(CMFCThreadDlg::ThreadFunc, this,
THREAD_PRIORITY_BELOW_NORMAL, 0, CREATE_SUSPENDED, NULL);
m_pThread->m_bAutoDelete = FALSE;
m_pThread->ResumeThread();

// mise à jour de l'état des boutons
UpdateButtons();
}

//***************************************************************************************
// OnBtnStop :
//***************************************************************************************
void CMFCThreadDlg::OnBtnStop()
{
// si aucun thread en cours
if(m_pThread == NULL)
return;

// arrêt du thread, si le thread était suspendu, on le reprend
m_bThreadRunning = FALSE;
if(m_bPause)
m_pThread->ResumeThread();
m_bPause = FALSE;

// on attend que le thread s'arrête (on traite les messages en attente car le thread
// est susceptible d'envoyer des messages aux contrôles créé par le thread principal)
while(::WaitForSingleObject(m_pThread->m_hThread, 10) != WAIT_OBJECT_0)
{
// traiter les messages en attente
MSG msg;
if(::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}

// destruction de l'objet CWinThread
delete m_pThread;
m_pThread = NULL;

// mise à jour de l'état des boutons
UpdateButtons();
}

//***************************************************************************************
// OnBtnPause :
//***************************************************************************************
void CMFCThreadDlg::OnBtnPause()
{
// si aucun thread en cours
if(m_pThread == NULL)
return;

// pause/reprise du thread
if(m_bPause)
{
// on affecte la nouvelle heure de départ
EnterCriticalSection(&m_cs);
m_dwStartTime = GetTickCount();
m_pThread->ResumeThread();
LeaveCriticalSection(&m_cs);
}
else
{
// on sauvegarde la valeur actuelle pour s'en servir d'offset
EnterCriticalSection(&m_cs);
m_pThread->SuspendThread();
m_dwOffsetTime = GetTickCount()-m_dwStartTime+m_dwOffsetTime;
LeaveCriticalSection(&m_cs);
}
m_bPause = !m_bPause;

// mise à jour de l'état des boutons
UpdateButtons();
}



IDD_MFCTHREAD DIALOGEX 0, 0, 250, 110
STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | 
    WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "MFCThread"
FONT 8, "MS Sans Serif"
BEGIN
    CTEXT           "00:00:00",IDC_MFCTHREAD_STC_COUNTER,10,10,230,70,
                    SS_CENTERIMAGE
    PUSHBUTTON      "Démarrer",IDC_MFCTHREAD_BTN_START,10,90,50,14
    PUSHBUTTON      "Arrêter",IDC_MFCTHREAD_BTN_STOP,70,90,50,14
    PUSHBUTTON      "Pause",IDC_MFCTHREAD_BTN_PAUSE,130,90,50,14
    PUSHBUTTON      "Fermer",IDCANCEL,190,90,50,14
END

0
Rejoignez-nous