Link to home
Start Free TrialLog in
Avatar of Dj_Fx8
Dj_Fx8

asked on

A Basic Thread Question

Hi

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

Steven
ASKER CERTIFIED SOLUTION
Avatar of DanRollins
DanRollins
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start 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" );
            return;
      }
      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
Avatar of Dj_Fx8
Dj_Fx8

ASKER

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

Steven

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
Avatar of Dj_Fx8

ASKER

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
    ((CCdBurnerDlg*)pUserData)->m_pgsProgress.SetPos(dwProgressInPercent);
    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())
            break;
    }
    return ((CCdBurnerDlg*)pUserData)->m_bAborted;
}

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

Steven
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) )  {
            AfxGetApp()->PumpMessage();
            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
Avatar of Dj_Fx8

ASKER

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'