Still celebrating National IT Professionals Day with 3 months of free Premium Membership. Use Code ITDAY17

x
?
Solved

A Basic Thread Question

Posted on 2003-12-06
7
Medium Priority
?
515 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
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 4
  • 3
7 Comments
 
LVL 49

Accepted Solution

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

Expert Comment

by:DanRollins
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" );
            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
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

Steven

0
Moving data to the cloud? Find out if you’re ready

Before moving to the cloud, it is important to carefully define your db needs, plan for the migration & understand prod. environment. This wp explains how to define what you need from a cloud provider, plan for the migration & what putting a cloud solution into practice entails.

 
LVL 49

Expert Comment

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

Author Comment

by:Dj_Fx8
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
    ((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
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) )  {
            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
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'
0

Featured Post

Learn how to optimize MySQL for your business need

With the increasing importance of apps & networks in both business & personal interconnections, perfor. has become one of the key metrics of successful communication. This ebook is a hands-on business-case-driven guide to understanding MySQL query parameter tuning & database perf

Question has a verified solution.

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

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 …
Introduction: Displaying information on the statusbar.   Continuing from the third article about sudoku.   Open the project in visual studio. Status bar – let’s display the timestamp there.  We need to get the timestamp from the document s…
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.
In this video, Percona Solution Engineer Rick Golba discuss how (and why) you implement high availability in a database environment. To discuss how Percona Consulting can help with your design and architecture needs for your database and infrastr…
Suggested Courses

670 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