A Basic Thread Question


My app has a modeless dialog which uses a 3rd party dll, now some calls to this dll take a bit of time to return, during which my main app locks,
1. Would I be right in thinking if this dialog had it's own thread this would prevent the main app from locking up, I know the dlg would still lock up
If so
2. Now the Dialog calls functions, passes and recieves data from the main app, if the dialog was in it's own thread do I need to do anything different for this communation with the main app
3. How do I set it up in it's own thread
4. Whats required to destroy the thread when the dialog closes
And finally
5. What are CSemaphores and Mutexes for, whould I need to use them

I have never worked with threads before and have done a bit of reading but still confused

Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Placing the entire modeless dialog onto a separate thread is fraught with *potential* problems.  Especially if it communicates with the main app... for instance if it calls member functions of a main window object.

Is it possible to isolate just the functions that take a long time?  Then in your dialog, it is a simple matter of:   Instead of calling the doggy function directly, create a thread that calls the function.

Starting a thread is quite simple, escpeciallu\y if you are using MFC:
Just use AfxBeginThread

If you simply must run the entire dialog on its own thread.  Let me know.  Otherwise show me a typical function that you must call and I'll show you how to call it on a separate thread.

-- Dan

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Semaphores and mutexes:
There comes a time when multiple threads of execution must access a resource.  I usually hit this when using a database connection, but it could be access to a globally-available linked list or anything that could cause problems if two things were accessing it at once.

If that could occur, you create a mutex.  And right before you access the resource, you check if some other thread is accessing it.  If so, the "checking process" just waits until the other thread is done.  It is easier to show than to explain

CMutex g_cMutex; // a global variable
void CDb::ExecuteSQL( LPCSTR lpszSQL )
      CSingleLock cLock( &g_cMutex );  // dtor will unlock

      if ( !cLock.Lock( CNUM_DbLockTimeoutMs ) ) { // 20 second timeout
            Log( "ERROR: ExecuteSQL() Thread Sync failed" );
            TRACE( "ERROR: ExecuteSQL() Thread Sync failed" );
      CDatabase::ExecuteSQL( lpszSQL );

Now a bunch of threads could pile up at the top of that function (in the cLock.Lock() fn) , but only one could be actually performing the "ExecuteSQL function.  As soon as one thread is done, it exits the fn, which destructs its copy of the CSingleLock and frees the global CMutex.  That lest another thread wake up from the Lock fn and proceeed to use the resource.  This is called "serializing" access to the resource.

From the desription of your problem, it is quite possible that you will not need to use these serialization techniques.

-- Dan
Dj_Fx8Author Commented:
Hi Dan

From what you have said I think the best way forward is as you suggest, to create a thread to call the function. I have showen below the two functions that I would need to do this to, they are two completely seperate functions in different dialogs

The first is
 m_hsChan = BASS_StreamCreateURL(cURL, 0, BASS_STREAM_META, &Proc_Download, (DWORD)this);

and the other is
int iRes =NeroBurn(m_ndhDeviceHandle, NERO_ISO_AUDIO_CD, m_pWriteCD,  dwFlags,      atoi(szSpeed), &m_npProgress);

As you see both are passed vars, and the &Proc_Download and &m_npProgress are callback functions, will this present a problem. Your help is greatly appreciated


Become a CompTIA Certified Healthcare IT Tech

This course will help prep you to earn the CompTIA Healthcare IT Technician certification showing that you have the knowledge and skills needed to succeed in installing, managing, and troubleshooting IT systems in medical and clinical settings.

An example:

static UINT MyThreadProc( LPVOID pParam )
      CMyDlg* pDlg = (CMyDlg*)pParam;
      pDlg=>m_fBurningNow= TRUE;

      int iRes= NeroBurn(pDlg->m_ndhDeviceHandle, NERO_ISO_AUDIO_CD,
            pDlg->m_pWriteCD, pDlg->m_dwFlags, atoi(pDlg->szSpeed),
            pfnBurnProgress );

      pDlg=>m_nBurnResult= iRes;
      pDlg=>m_fBurningNow= FALSE;
      return 0;   // thread completed successfully
void CMyDlg::OnButton1()
      AfxBeginThread( MyThreadProc, this );

On click of the button, the thread starts.  It passes in a pointer to the CDialog-derived object so that the thread proc can access member variables of the dialog.

Your dialog would need to keep an eye on the variable named m_fBuriningNow.  You should add a Window Timer to the CMyDlg to take care of that -- say check it once per second and when if goes TRUE, the burn is done and the result is in m_nBurnResult.

I "shined it" on the callback proc used in the NeroBurn function, because I don't know what NeroBurn needs there.  Perhaps you have already figured out how to handle that issue.

Here is the critical thing about that NeroBurn callback... you must not directly access any CWnd-derived objecst from that callback becasue it will now be running on a separate thread (you will get MFC ASSERTs that protect you from serious errors).  My guess is that you want to update a CProgressbar in that callback, but you should not -- at least not directly.

The best way to handle that NeroBurn callback is to simply have it set a memory variables that indicate how much progress has taken place.  Do the actual progressbar updating in the Window Timer -- it runs on the main U/I thread.

One other note:  
You will usually need to prevent the user from closing the dialogbox until the thread is finished.   If the CDialog object destructs, then the thread's pDlg will become invalid which would cause all kinds of problems.

If you need help setting up the NeroBurn callback, show the prototype that the function expects you to use and describe it a bit.

-- Dan
Dj_Fx8Author Commented:
Hi Dan

Forgive me if I'm acting a bit dumb here :-), but being self taught,  I hope you forgive me.

  >>> simply have it set a memory variables that indicate...   Not sure about this bit

Nero uses about 6 different callbacks and these are passed to the NeroBurn function by the &m_npProgress, which is actually a structure, but I don't think this will make any difference to what we are discussing other than I have to rework 6 callbacks. Five of them are very simular and if I had one reworked I should have no probs doing the other 4, the 6th one..... well I show it below as well to see what you think

The first five are something like this (others differ by instead of  a DWORD it is const char*)
BOOL NERO_CALLBACK_ATTR CCdBurnerDlg::ProgressCallback(void *pUserData, DWORD dwProgressInPercent)
    //pUserData will be the Dialogs this pointer
    CString szTemp;
    szTemp.Format("%d", dwProgressInPercent);
    szTemp += "%";
    ((CCdBurnerDlg*)pUserData)->SetDlgItemText(IDC_STATIC_PERCENTDONE, szTemp);  //display the % done in a CStatic
    ((CCdBurnerDlg*)pUserData)->AppendString("Phase: " + szTemp);  //AppendString is a function in my dialog
    return ((CCdBurnerDlg*)pUserData)->m_bAborted;  //m_bAborted is a bool flag to indicate the user has canceled operation

Now this is the 6th one which I have no idea about, but am sure you'll understand it (Nero say "The first partof the function ensures that, while Nero is burning, the application still can process messages")
BOOL NERO_CALLBACK_ATTR CCdBurnerDlg::IdleCallback(void *pUserData)
    static MSG msg;
    while(!(((CCdBurnerDlg*)pUserData)->m_bAborted) && ::PeekMessage(&msg,NULL, NULL, NULL, PM_NOREMOVE))      {
        if (!AfxGetThread()->PumpMessage())
    return ((CCdBurnerDlg*)pUserData)->m_bAborted;

Once again many thanks for your help, I NEVER would have figured this all out, on my own

Here is what I mean:

BOOL NERO_CALLBACK_ATTR CCdBurnerDlg::ProgressCallback(void *pUserData, DWORD dwProgressInPercent)
        CCdBurnerDlg* pDlg= (CCdBurnerDlg*) pUserData;
        pDlg->m_pgsProgress.m_nPctDone= dwProgressInPercent;
        return( pDlg->m_bAborted );

Then in your timer, which gets called once per second of 500 ms or whatever,
CCdBurnerDlg::OnTimer( ... )
      m_pgsProgress.SetPos( m_nPctDone );
      CString sTmp; sTmp.Format("%d%%", m_nPctDone);
      m_pgsProgress.SetDlgItemText(... etc... )

On the IdleCallback, I think you can simple return the m_bAborted status.

Actually, I didn't know that there was an IdleCallback.  The NeroBurn function appears to be providing a mechanism that would allow you to avoid multi-threading altogether.  If it were working the way it should, you should not experience the U/I delays that you describe when you say "during which my main app locks"

Perhaps there would be an initial delay -- as the system identifies and checks the drive and the disk, but once the function is running, you should be able to drag the dialog box, click buttons, and otherwise have a responsive U/I.

If you do not have a responsive U/I, before starting down the multithreading path, you can try

      MSG msg;
      while ( ::PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE) )  {
            if (m_bAbort) break;

Normally, I wouldn't think AfxGetApp() will be any different from AfxGetThread() in the case of a single-threaded app, but maybe NeroBurn has started a thread or something...

-- Dan
Dj_Fx8Author Commented:
Hi Dan

When I said my app locks up I didn't mean during the entire burn process, it was only just after writing the lead in for about 5secs, but as my app will prob be playing music during this time it's most annoying. Anyway I have got my thread working nicely, thanks to your expert help, you deserve a Grade A++
On a closing note, I have a few questions about threading my other function as I can fore see a slight prob, but that's for new question, as I feel you have done quite enough for the points in this one, I'm going to tidy up my code and then I post a new question to you, if you don't mind, with a subject of 'Follow up question for Dan'
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
System Programming

From novice to tech pro — start learning today.