//---- First example: Using global vars and fn
int gnCurValue;
int gnMaxValue;
BOOL gfStopNow;
UINT MyThreadProc( LPVOID pParam )
{
while ( !gfStopNow && (gnCurValue < gnMaxValue) ) {
gnCurValue++;
Sleep( 50 ); // would do some work here
}
return(1);
}
void CThreadTesterDlg::OnBnClickedStart()
{
gnCurValue= 0;
gnMaxValue= 5000;
gfStopNow= 0;
m_ctlStatus.SetWindowText("Starting...");
SetTimer( 1234, 333, 0 ); // 3 times per second
AfxBeginThread( MyThreadProc, 0 ); // <<== START THE THREAD
}
void CThreadTesterDlg::OnBnClickedStop()
{
gfStopNow= TRUE;
KillTimer( 1234 );
m_ctlStatus.SetWindowText( "STOPPED" );
}
void CThreadTesterDlg::OnTimer(UINT_PTR nIDEvent)
{
CString sStatusMsg;
sStatusMsg.Format("Running: %d", gnCurValue );
m_ctlStatus.SetWindowText( sStatusMsg );
CDialog::OnTimer(nIDEvent);
}
We used AfxBeginThread on line 22 to spin off the secondary worker thread. We could just as easily have used the CreateThread API function. The former ends up at the latter (by way of the _beginthread C-Runtime function).
class CFileCollector
{
public:
CFileCollector::CFileCollector() {
m_fAbortNow= m_nCntFiles= m_nCntDirs= 0;
};
CStringArray m_asAllNames;
BOOL m_fDone;
int m_nCntFiles;
int m_nCntDirs;
CString m_sStartDir;
CString m_sStatusText;
CWinThread* m_pcThread;
void Start( LPCSTR szStartDir ) {
m_fAbortNow= FALSE;
m_nCntFiles= m_nCntDirs= 0;
m_sStatusText= "Starting...";
m_sStartDir= szStartDir;
m_pcThread=
AfxBeginThread( FileCollectorThreadProc, this );
}
void StopNow(void) {
m_fAbortNow= TRUE;
}
private:
BOOL m_fAbortNow;
static UINT FileCollectorThreadProc( LPVOID pParam ) {
CFileCollector* pThis= (CFileCollector*)pParam;
UINT nRet= pThis->DoThreadProc(); // get out of 'static mode'
return( nRet );
}
UINT DoThreadProc() {
m_fDone= FALSE;
AllFilesInDirAndSubdirs( m_sStartDir ); // curse and recurse!
m_sStatusText.Format("DONE: Found %d Files in %d Folders", m_nCntFiles, m_nCntDirs );
if ( m_fAbortNow ) m_sStatusText= "Halted by User";
m_fDone= TRUE;
return( 0 );
}
void AllFilesInDirAndSubdirs( LPCSTR szDir ) {
CFileFind cFF;
CString sWildcard= CString(szDir)+ "\\*.*";
BOOL fFound= cFF.FindFile( sWildcard );
while( fFound && !m_fAbortNow ) { // check for abort request
fFound= cFF.FindNextFile();
if ( cFF.IsDots() ) { // ignore . and ..
continue;
}
if ( cFF.IsDirectory() ) { // if directory...
m_nCntDirs++;
AllFilesInDirAndSubdirs( cFF.GetFilePath() ); // recurse!
continue;
}
// else, regular file
m_asAllNames.Add( cFF.GetFilePath() ); // add one filename to list
m_nCntFiles++;
m_sStatusText.Format("Found Folders: %6d Files: %9d", m_nCntDirs, m_nCntFiles );
}
cFF.Close();
}
};
I used an important "trick" in that example, and it's worth knowing. The ThreadProc must be a regular _cdecl function, meaning it can be a global function (not within an object) or an object member function that is declared as static. In lines 29 to 33, I create a static function that I can use as a "gateway" into the object -- a technique that is useful in many situations. See How to provide a CALLBACK Function Into a C++ Class Object for a detailed description of the technique.
#define IDT_Status 1234
void CThreadTesterDlg::OnBnClickedStartthread()
{
GetDlgItem(IDPB_StartThread)->EnableWindow( FALSE);
GetDlgItem(IDPB_StopThread)->EnableWindow( TRUE );
SetTimer( IDT_Status, 100, 0 ); // 10 times per second
m_cFileCollector.Start( "C:" ); // <<<=== GO!!!
}
void CThreadTesterDlg::OnBnClickedStopthread()
{
m_cFileCollector.StopNow(); // <<<===STOP!!!
GetDlgItem(IDPB_StopThread)->EnableWindow( FALSE );
GetDlgItem(IDPB_StartThread)->EnableWindow( TRUE );
}
//-------------------------------- screen updates occur here
void CThreadTesterDlg::OnTimer(UINT_PTR nIDEvent)
{
m_ctlStatus.SetWindowText( m_cFileCollector.m_sStatusText );
if ( m_cFileCollector.m_fDone ) {
KillTimer( IDT_Status );
GetDlgItem(IDPB_StopThread)->EnableWindow( FALSE );
GetDlgItem(IDPB_StartThread)->EnableWindow( TRUE );
}
CDialog::OnTimer(nIDEvent);
}
Notes:
m_cFileCollector.StopNow(); // try to stop normally
Sleep(1000);
HANDLE hThread=m_cFileCollector.m_pcThread->m_hThread;
DWORD nExitCode;
BOOL fRet= ::GetExitCodeThread( hThread, &nExitCode );
if ( fRet && nExitCode==STILL_ACTIVE ) { // did not die!
TerminateThread( hThread, -1 ); // <<== Kill it (but not cleanly)
}
The problem with this code (and it can be quite a significant problem) is that it does not let the thread clean up after itself. It never falls off the end of its ThreadProc (or other, deeply-nested procedure frames), so object destructors do not get called.
Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.
Comments (0)