|
-
April 3rd, 2009, 07:28 AM
#1
MFC - Cdialog::OnOK() in Thread
Hi,
I've a problem, which i don't know how to solve.
I've a class LoginDlg, which is defined this way
Code:
class LoginDlg : public CDialog
Dialog has one button, which has method
Code:
void LoginDlg::OnBnClickedOk()
In this method a I create a Thread
Code:
pLoginThread = AfxBeginThread(LogIn, myUserInfo, THREAD_PRIORITY_NORMAL);
I want to call OnOK() method after pLoginThread finish its job. Part of code is:
Code:
UINT LogIn(LPVOID lParam) {
// Any code... (Downloading file)
return 1;
}
void LoginDlg::OnBnClickedOk() {
CWinThread* pLoginThread = AfxBeginThread(LogIn, myUserInfo, THREAD_PRIORITY_NORMAL);
// If i write OnOK() here, it doesn't wait on finishing thread
}
I really don't know, how to do it. So please, help me
-
April 3rd, 2009, 07:38 AM
#2
Re: MFC - Cdialog::OnOK() in Thread
you can call a method instead of creating a thread, is there any specific reason to do that??
-
April 3rd, 2009, 07:52 AM
#3
Re: MFC - Cdialog::OnOK() in Thread
Yes, there is a reason.
If I would call a method instead of creating thread, Application would be frozen for a while.
-
April 3rd, 2009, 09:02 AM
#4
Re: MFC - Cdialog::OnOK() in Thread
One solution is post an user message from the thread function and call OnOK from its handler function.
See next example:
Code:
// TestOKDlg.h
#define WM_DO_END_DIALOG (WM_APP + 1)
class CTestOKDlg : public CDialog
{
//...
afx_msg LRESULT OnDoEndDialog(WPARAM wParam, LPARAM lParam);
};
Code:
// TestOKDlg.cpp
BEGIN_MESSAGE_MAP(CTestOKDlg, CDialog)
// ...
ON_MESSAGE(WM_DO_END_DIALOG, OnDoEndDialog)
END_MESSAGE_MAP()
//...
UINT AFX_CDECL LogIn(LPVOID lpParam)
{
// ...some very long task...
::PostMessage((HWND)lpParam, WM_DO_END_DIALOG, 0, 0);
return 0;
}
void CTestOKDlg::OnBnClickedOk()
{
AfxBeginThread(LogIn, m_hWnd, THREAD_PRIORITY_NORMAL);
}
LRESULT CTestOKDlg::OnDoEndDialog(WPARAM wParam, LPARAM lParam)
{
OnOK();
return 0;
}
Last edited by ovidiucucu; April 3rd, 2009 at 09:04 AM.
-
April 3rd, 2009, 09:12 AM
#5
Re: MFC - Cdialog::OnOK() in Thread
I'm not sure what you are trying to do with the Dialog box. It sounds like you want a screen on the app which is active while downloading a file, or you want to continue using the dialog box while downloading a file.
Anyhow, if your dialog box is modal, you are going be stuck with in dialog box while downloading.
If I wanted to continue using the app while download, I would start thread and send it a pointer to the Dialog box once you started it, then execute OnOK() within the thread when it's done downloading:
Code:
AfxBeginThread(LogIn, myUserInfo, THREAD_PRIORITY_NORMAL, LoginDlg *pDlg ){
... dosomething
AfxMessageBox("Thread Done, Closing Dialog Box");
pDlg->OnOk();
....
pDlg->CallanyFunctionIntheDialogBox();
pDlg->AccessAnyMemberInDialog;
pDlg->DoAnythingToTheDialogBoxorItItsParentorParentVars;
pDlg->I_Own_You;
};
AfxBeginThread(......., ...., this );
...If I wanted to use a dialog box/app until the download is done, That's how I would do it, ..... but, if that where the case a Modless Dialog would do the job better called with in the App/Dialog/CFormView.
Or you create your dialog box as Modless box, which the Thread closes when it is done.
If you want to start a thread and update a dialog box/app during certain points in the thread, I would think you have to pass a pointer to the DialogBox/App/View/Docuement, .... etc.
Try passing a pointer to the dialog box in your thread. I haven't done it, but I pass pointers to other dialogboxes from other dialog boxes all the time. In my apps I have about 20 dialog boxes which I can access any button, function, variable, etc at anytime.
...I havn't done much with threads, but I think it should work.
Last edited by ADSOFT; April 3rd, 2009 at 09:27 AM.
Rate this post if it helped you.
-
April 3rd, 2009, 10:52 AM
#6
Re: MFC - Cdialog::OnOK() in Thread
2 Bald3rr and ADSOFT:
the only acceptable solution is the one provided by ovidiucucu.
Some more information about using working threads you could find here
Victor Nijegorodov
-
April 3rd, 2009, 01:18 PM
#7
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by VictorN
2 Bald3rr and ADSOFT:
the only acceptable solution is the one provided by ovidiucucu.
Some more information about using working threads you could find here
Maybe accepatable to you, but I wouldn't do it that way.
Rate this post if it helped you.
-
April 3rd, 2009, 02:15 PM
#8
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by ADSOFT
Maybe accepatable to you, but I wouldn't do it that way.
Then you will be in trouble!
Victor Nijegorodov
-
April 3rd, 2009, 03:03 PM
#9
Re: MFC - Cdialog::OnOK() in Thread
Thanks everyone, especially to ovidiucucu. It's working fine.
-
April 3rd, 2009, 06:12 PM
#10
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by VictorN
Then you will be in trouble! 
I doubt it, I bet I can get a thread to work that that I pass a pointer(to the thread function that is) to a dialog box or better yet a pointer to my View Class with out having to write all that Code the OV did. With a pointer to my View Class I can control anything in View Class, it's related Dialog Boxes and the CDocument Class, and probobly the whole App. Heck the thread could be a monitor to the app makeing sure that the user hasn't done anything to violate integrity or I could update the Application or it's Database without the user knowing it, do backups, ....etc, while working out of just one thread.
That's cool if OV wants to do it with all that code, to each his own! However; ...."There's more than one way to skin a cat!" I would rather just pass one pointer and go from there.
Last edited by ADSOFT; April 3rd, 2009 at 06:18 PM.
Rate this post if it helped you.
-
April 3rd, 2009, 06:53 PM
#11
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by ADSOFT
With a pointer to my View Class I can control anything in View Class, it's related Dialog Boxes and the CDocument Class, and probobly the whole App.
The bottom line is that any shared data needs to be protected when accessed from multiple threads. When you simply pass a view pointer to the thread and access MFC controls within the thread, you aren't synchronizing data access properly and it will lead to race conditions.
-
April 3rd, 2009, 07:28 PM
#12
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by Arjay
The bottom line is that any shared data needs to be protected when accessed from multiple threads. When you simply pass a view pointer to the thread and access MFC controls within the thread, you aren't synchronizing data access properly and it will lead to race conditions.
Well I can tell you know about threads, and when it comes to data synchronizing it's all how you write the thread.
The OP wanted to access a control from within a thread, I just took it to another level. Data synchronization is not just related to threads, it must also be taken into consideration at the application level.
The general topic here is access to controls from a worker threads.
Rate this post if it helped you.
-
April 3rd, 2009, 08:44 PM
#13
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by ADSOFT
The OP wanted to access a control from within a thread, I just took it to another level. Data synchronization is not just related to threads, it must also be taken into consideration at the application level.
Bottom line is that you shouldn't access MFC controls from a secondary thread.
Sure, you can pass a pointer of an MFC object to a secondary thread, but if you need to access MFC class functionality from the pointer, you'll need to provide your own synchronization. You can do that using the method that Ovidiu and Victor were talking about or you can create thread safe accessor methods.
-
April 4th, 2009, 01:18 AM
#14
Re: MFC - Cdialog::OnOK() in Thread
 Originally Posted by Arjay
Bottom line is that you shouldn't access MFC controls from a secondary thread.
Sure, you can pass a pointer of an MFC object to a secondary thread, but if you need to access MFC class functionality from the pointer, you'll need to provide your own synchronization. You can do that using the method that Ovidiu and Victor were talking about or you can create thread safe accessor methods.
Ok, well I'm just getting into threads and it seems that you have tons of experience with them.
.... here's what I've ended up with, I'm hungry so I have to stop for a while(maybe you will respond while I get some dinner); I sent the threading function a this pointer and in the worker thread recast it back to a CFormview which has tons of buttons. It works but I keep getting the following Assertion:
HTML Code:
CObject* p;
ASSERT((p = pMap->LookupPermanent(m_hWnd)) != NULL ||
(p = pMap->LookupTemporary(m_hWnd)) != NULL);
ASSERT((CWnd*)p == this); // must be us
// Note: if either of the above asserts fire and you are
// writing a multithreaded application, it is likely that
// you have passed a C++ object from one thread to another
// and have used that object in a way that was not intended.
// (only simple inline wrapper functions should be used)
//
// In general, CWnd objects should be passed by HWND from
// one thread to another. The receiving thread can wrap
// the HWND with a CWnd object by using CWnd::FromHandle.
//
// It is dangerous to pass C++ objects from one thread to
// another, unless the objects are designed to be used in
// such a manner.
What's a "simple inline wrapper function"? that's probobly why CWD::FormHandle didn't work.
I understand that the SendMessage via m_hwnd approach works, however it seems like Microsoft is implying to use simple wrapper function with CWD::FormHandle ....
Last edited by ADSOFT; April 4th, 2009 at 01:21 AM.
Rate this post if it helped you.
-
April 4th, 2009, 02:36 AM
#15
Re: MFC - Cdialog::OnOK() in Thread
This is why you can't usually pass MFC objects to another thread. The simple wrapper objects that the MFC comments are referring to are the lightweight inlined functions that MFC uses to send a message to a control. Anything that causes MFC to walk its internal maps or access more complex functionality will be likely to fail.
At any rate, take a look at the sample code in the post: Sharing a thread safe std::queue between threads w/progress bar updating
This sample includes code that shares a thread safe std::queue between two worker threads and the main UI thread. The worker threads push items onto the queue (with each worker thread pushing the items on at different rates). The UI thread gets signalled when an item is available in the queue via PostMessage and a user defined message. The pushing the items on the queue and popping them off the queue are done in a thread safe manner (using a critical section lock). The threading and queue access is wrapped into a progress manager class so that the thread management and queue synchronization is encapsulated within the class.
This approach makes a clean interface between the MFC UI code and the thread code. It makes debugging easier because the UI logic (enabling/disabling buttons, changing state, etc.) can be worked out separately from the threading code. Likewise, the threading code can be debugged separately from the UI code (by calling the methods of the progress class directly).
Tying this back to the post's related topics, you'll notice I pass in a hWnd from the dialog in the CProgressMgr::Start method. This hWnd lets the threads know where to send the queue notification messages.
Start (et al) button handler code:
Code:
//+------------------------------------------------------
// Start, pause, and resume button handler
//+------------------------------------------------------
void CStartStopDlg::OnBnClickedStartPauseResume()
{
m_btnStartPauseResume.EnableWindow( FALSE );
m_btnStop.EnableWindow( FALSE );
switch( ToggleSPRState( ) )
{
case TS_START:
// Remove the list control items
m_ctrlList.DeleteAllItems( );
m_uListItemCount = 0;
// Start the threads
m_ProgressMgr.Start( GetSafeHwnd( ) );
break;
case TS_PAUSE:
// Pause the thread
m_ProgressMgr.Pause( );
break;
case TS_RESUME:
// Resume the thread
m_ProgressMgr.Resume( );
m_ThreadState = TS_START;
break;
default:
ASSERT( 0 ); // We shouldn't reach this
}
m_btnStartPauseResume.EnableWindow( TRUE );
m_btnStop.EnableWindow( TRUE );
}
WM_USER_INC_PROGRESS handler code. This message gets sent by the secondary thread to notify the main thread that a new item has been pushed on the queue. Sorry about using the magic numbers in the case statements. They represent T1 and T2 (but my bad for using magic numbers).
Code:
//+------------------------------------------------------
// OnIncProgress handler. Receives user defined message
// posted from the secondary thread. When we receive this
// message we increment the progress bar and add the item
// to the list control.
//+------------------------------------------------------
LRESULT CStartStopDlg::OnIncProgress( WPARAM wParam, LPARAM lParam )
{
UINT uID = (UINT) lParam;
if( TS_START != m_ThreadState ) return 0;
// Increment the progress controls
switch( uID )
{
case 1:
m_ctrlProgress1.StepIt( );
break;
case 2:
m_ctrlProgress2.StepIt( );
break;
}
// Get the item out of the queue and put it in the list control
InsertItemIntoListView( );
return 1;
}
Let's look at the InsertItemIntoListView( ) method referenced above. This method accesses the first item in the queue, inserts it into the listview control and then deletes it from the queue. You'll notice that there doesn't appear to be any thread synchronization (because it's encapsulated entirely within the CProgressMgr thread).
Code:
//+------------------------------------------------------
// Called when a notification message has been received from
// one of the secondary threads. Removes one or more items
// from the shared queue in a thread safe manner and inserts
// the item into the list control.
//+------------------------------------------------------
void CStartStopDlg::InsertItemIntoListView( )
{
CItem* pItem = NULL;
CString sCount;
CString sThreadID;
CString sThreadCount;
// Retrieve the next queue item (thread-safe method)
while( NULL != ( pItem = m_ProgressMgr.GetNextItem( ) ) )
{
// Format the listview column strings
sCount.Format( _T( "%d" ), m_uListItemCount );
sThreadID.Format( _T( "This is an item from thread: %d" ), pItem->GetID( ) );
sThreadCount.Format( _T( "%d" ), pItem->GetCount( ) );
// Insert the item into the list view and ensure the item is visible
m_ctrlList.InsertItem( m_uListItemCount, sCount );
m_ctrlList.SetItemText( m_uListItemCount, 1, sThreadID );
m_ctrlList.SetItemText( m_uListItemCount, 2, sThreadCount );
m_ctrlList.EnsureVisible( m_uListItemCount, TRUE );
// Remove this item from the queue (thread-safe method)
m_ProgressMgr.PopItem( );
m_uListItemCount++;
}
}
Let's take a look at the thread safe queue accessor methods. There are 3 of them: The GetNextItem and PopItem methods that are accessed by the UI thread and the PushItem method which is accessed by the secondary threads.
Code:
// Gets the next item from the ItemQueue
CItem* GetNextItem( )
{
CAutoLockT< CItemQueue > lock(&m_ItemQueue);
if( TRUE != m_ItemQueue.empty( ) )
{
return m_ItemQueue.front( );
}
return NULL;
}
// Removes the front item from the ItemQueue
void PopItem( )
{
CAutoLockT< CItemQueue > lock( &m_ItemQueue );
if( TRUE != m_ItemQueue.empty( ) )
{
delete m_ItemQueue.front( );
m_ItemQueue.pop( );
}
}
HRESULT PushItem( CItem* pItem )
{
HRESULT hr = S_OK;
CAutoLockT< CItemQueue > lock( &m_ItemQueue );
try
{
m_ItemQueue.push( pItem );
}
catch( std::bad_alloc )
{
hr = E_OUTOFMEMORY;
}
return hr;
}
The thread safety of these methods may not be obvious, but they are handled by the CAutoLockT class which is locking a wrapper class to a
critical section object. This class simply obtains a critical section lock (EnterCriticalSection(...)) in its constructor and releases the lock (LeaveCriticalSection(...)) in its destructor which gets called when the CAutoLockT instance goes out of scope. The single line of code that gets this done ensures the queue is only ever accessed by one thread at a time.
Last edited by Arjay; April 4th, 2009 at 02:40 AM.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|