Go Premium for a chance to win a PS4. Enter to Win


A Basic Thread Question

Posted on 2003-12-06
Medium Priority
Last Modified: 2013-11-20

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

Question by:Dj_Fx8
  • 4
  • 3
LVL 49

Accepted Solution

DanRollins earned 2000 total points
ID: 9891452
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
LVL 49

Expert Comment

ID: 9891527
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

Author Comment

ID: 9892446
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



Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

LVL 49

Expert Comment

ID: 9895087
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

Author Comment

ID: 9899951
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

LVL 49

Expert Comment

ID: 9900824
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

Author Comment

ID: 9907859
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'

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Here is how to use MFC's automatic Radio Button handling in your dialog boxes and forms.  Beginner programmers usually start with a OnClick handler for each radio button and that's just not the right way to go.  MFC has a very cool system for handli…
In this article, I'll describe -- and show pictures of -- some of the significant additions that have been made available to programmers in the MFC Feature Pack for Visual C++ 2008.  These same feature are in the MFC libraries that come with Visual …
This video will show you how to get GIT to work in Eclipse.   It will walk you through how to install the EGit plugin in eclipse and how to checkout an existing repository.
Whether it be Exchange Server Crash Issues, Dirty Shutdown Errors or Failed to mount error, Stellar Phoenix Mailbox Exchange Recovery has always got your back. With the help of its easy to understand user interface and 3 simple steps recovery proced…

783 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question