Solved

A Basic Thread Question

Posted on 2003-12-06
7
474 Views
Last Modified: 2013-11-20
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
0
Comment
Question by:Dj_Fx8
  • 4
  • 3
7 Comments
 
LVL 49

Accepted Solution

by:
DanRollins earned 500 total points
Comment Utility
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
0
 
LVL 49

Expert Comment

by:DanRollins
Comment Utility
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
0
 

Author Comment

by:Dj_Fx8
Comment Utility
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

0
What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

 
LVL 49

Expert Comment

by:DanRollins
Comment Utility
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
0
 

Author Comment

by:Dj_Fx8
Comment Utility
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
0
 
LVL 49

Expert Comment

by:DanRollins
Comment Utility
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
0
 

Author Comment

by:Dj_Fx8
Comment Utility
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'
0

Featured Post

What Should I Do With This Threat Intelligence?

Are you wondering if you actually need threat intelligence? The answer is yes. We explain the basics for creating useful threat intelligence.

Join & Write a Comment

Introduction: Load and Save to file, Document-View interaction inside the SDI. Continuing from the second article about sudoku.   Open the project in visual studio. From the class view select CSudokuDoc and double click to open the header …
Introduction: The undo support, implementing a stack. Continuing from the eigth article about sudoku.   We need a mechanism to keep track of the digits entered so as to implement an undo mechanism.  This should be a ‘Last In First Out’ collec…
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.
Excel styles will make formatting consistent and let you apply and change formatting faster. In this tutorial, you'll learn how to use Excel's built-in styles, how to modify styles, and how to create your own. You'll also learn how to use your custo…

771 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

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now