|
Thanks, but I really can't take much credit. I just did as Lars Haendel suggested on his site.
Jens Winslow
|
|
|
|
|
It occurred to me that people might wonder why I choose the more complicated use of a Functor instead of just using a structure with a this pointer and data parameters (now that I am done, I wonder too).
Let me share with you a tale of mystery and wonder - a tale of a painful journey!
First I did try passing a pointer to a structure...
In my first structure based approach I defined a structure and enumerator to pass to a static UINT Command_T( LPVOID pCmdFunctor) which I could call from AfxBeginThread.
<br />
enum EChanCmd {<br />
INITIALIZE,<br />
SERVERCONNECT,<br />
CLIENTCONNECT,<br />
SEND,<br />
RECEIVE,<br />
CANCEL<br />
};<br />
<br />
struct SChanCmd {<br />
CMuxChannel* pChan;<br />
SMuxChannelEventData* pParam;<br />
EChanCmd eCmd;<br />
}<br />
Command_T is a switch statement that then calls the appropriate command function in the CMuxChannel object pointed to by pChan. For debugging I used a TRACE before and after the call, calling pChan->GetStatus(), a member function created to tell me the status .
At first things seemed to work fine . It was a bit of a pain to declare SChanCmd every time I wanted to issue a command with a worker thread, making for slightly complicated code like:
<br />
SChanCmd uCmd;<br />
uCmd.pParam = new SMuxChannelEventData;
uCmd.pParam->wp = (WPARAM)(USHORT)m_uiPortNum;<br />
uCmd.pParam->lp = (LPARAM)(LPCTSTR)m_strIPAddr;<br />
uCmd.eCmd = CMuxChannel::CLIENTCONNECT;
uCmd.pChan = &m_Chan;<br />
AfxBeginThread(CMuxChannel::Command_T, &uCmd);<br />
Add to this a large ugly switch statement in Command_T, and my code was esteticaly not to pleasing. But after 3 days of battling with it I did not care!
Then I hit a minor snag.
Some of my CMuxChannel commands might throw an exception (which are appropriately handled in the class). To my surprise, if an exception occurs my uCmd->pChan is set to NULL!!!! God only knows what else may be happening! I am guessing that the unwinding of the heap / stack occurring during a throw/catch exception somehow blows away my pointer (I tried saving a backup and using it, but that did not work).
What ever is happening made me very nervous about the condition of m_Chan, and my ability to call functions from AfxBeginThread.
I then tried using the Functor template (see link above), but I was not quite able to get it working, so I created my own less generic functor specifically for CMuxChannel. The concept was simple:
A class with a pointer to an instance of CMuxChannel, a function pointer to a command function in CMuxChannel, and a pointer to the parameter. Add a Call function to do the call, and a constructor taking a pointer to CMuxChannel and the desired function, and BINGO!
Now I can call my function like this, which looks a lot better:
<br />
CMuxChannel::CCmdFunctor uCmd(&m_Chan, CMuxChannel::ConnectAsClient);<br />
AfxBeginThread(CMuxChannel::Command_T, &uCmd);<br />
In reality the syntax for my Functor was murder to get right (had I known I would not have bothered, and just wrapped each command function in a static UINT AfxBeginThread function) But I did get it working at last.
<br />
class CCmdFunctor {<br />
public:<br />
CCmdFunctor(CMuxChannel* pChan, void (CMuxChannel::*ptrCmdFunc)(SMuxChannelEventData*), SMuxChannelEventData* pParam = NULL);<br />
<br />
void Call() { (*m_pChan.*m_ptrCmdFunc)(m_pParam);}<br />
<br />
CMuxChannel* m_pChan;
private:<br />
<br />
void (CMuxChannel::*m_ptrCmdFunc)(SMuxChannelEventData*);<br />
SMuxChannelEventData* m_pParam;<br />
};<br />
<br />
static UINT Command_T( LPVOID pCmdFunctor);
In conclusion:
1) Functors, multi-threaded, and function pointers are not for the faint of heart!
2) Something strange happens to your pointers to objects if the object uses / throws exceptions internally (feel free to explain this if you can)
3) Why did I ever want to become a programmer anyway?
Perhaps this will help someone else (I hope)
Me, I am thinking about becoming a farmer
Perhaps this was not a good day to code after all;P
Jens Winslow
|
|
|
|
|
I had a rather nasty surprice...
Since the functor is called by AfxBeginThread, we must be carefull that Functor does not go out of scope before AfxBeginThread has called the Call function, or nasty things happen, like access violations and other randomness
My solution: create Functor on heap with new
pCmd = new CMuxChannel::CCmdFunctor(&m_Chan, CMuxChannel::ConnectAsServer);
and have my Command_T function responsible for deleting it, when thread exits and we presumably are done with info in Functor)
UINT CMuxChannel::Command_T( LPVOID pCmdFunctor )<br />
{<br />
CCmdFunctor* p = (CCmdFunctor*)pCmdFunctor;<br />
CString str = p->GetChan()->GetState();<br />
p->Call();<br />
delete p;
str = str + " -> " + p->GetChan()->GetState();<br />
TRACE1("\nSM: %s ",str); <br />
return 0;<br />
}
Jens Winslow
|
|
|
|
|
|
WOW... this the the thread I've been looking for for quite some time... I was having so much trouble getting access to my dialog classes controlls from within my worker thread.. I'm gonna see if I can code this example of the functor approach in, from the looks of it, I think this is gonna work out... Thank You
|
|
|
|
|
I also have the same problem
|
|
|
|
|
tc->_this->UpdateData( TRUE ) gets an assertion failure. Why is this?
|
|
|
|
|
You should not get an assertion failure. I tried it and it works fine. Maybe your dialog isn'n created when you call UpdateDate. Do you have more details?
Enis Arif
-----------
"I am enough of an artist to draw freely upon my imagination. Imagination is more important than knowledge. Knowledge is limited. Imagination encircles the world." (Albert Einstein)
|
|
|
|
|
I re-downloaded your example (i.e. started from scratch) and made the while loop in the StartThread function look like this:
while (ts->_this->m_ctrl_progress.GetPos () < 1000)
{
Sleep(500);
ts->_this->m_ctrl_progress.StepIt ();
ts->_this->UpdateData( TRUE );
}
Running the program (in debug mode) gives an assertion failure. Underneath the assertion failure is the message (in wincore.cpp):
// 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.
Doesn't the ts->_this-> portion take care of the window handle?
|
|
|
|
|
I tried this in VC6, and it does not seem to work, no update occurs.
Jens
|
|
|
|
|
I also have the same problem. I'm trying to insert text into an edit control and UpdateData via the "ts" pointer and I get an assertion failure. Has anyone come up with a solution yet?
|
|
|
|
|
Assert error is a must for high VC liks VC7.
CDialog is base CWnd, it has own thread.
Now you access CDialog member function in other working thread.
If the member function allows you to read things. It is OK.
But if it changes things or affect the CDialog variables which are under the scope of its own thread. Then you will get coredump, memory leak or assertion error (depends on your compiler and debugger settings)
Also, MFC class is not thread safe.
Thread to MFC Thread.
The easy way is to send message.
Let the MFC Thread to handle the changes.
Or you have to create critical section and comparment which are not easy to made.
The concepts and the mistakes here are indeed quite common mistake for those who understands MFC clearly.
A reference book : Visual C++.Net Bible - Chapter for thread
|
|
|
|
|
I am new to MFC, just trying out with some self-projects related to file transfer using serial communication. when I tried using Threads, I have same problem with "UpdataData". I referred the above posted code. So is there any solution in VC++ 6.0?
Pls. let me know.
|
|
|
|
|
To solve this problem with calling UpdateData(); you must create CWnd* pionter using methids this->FromHandle(this->m_phWnd); then convert pointer to you window class . It happen becuse you must get window objet nit a pinter to class associatet with window
Gress
|
|
|
|
|
You should not be using UpdateData() , let alone in a secondary thread! To properly update the UI from a secondary thread, the only safe way is to post a message back to the primary thread (the one that owns the UI) indicating what needs to be updated. Read this article for more.
"When I was born I was so surprised that I didn't talk for a year and a half." - Gracie Allen
|
|
|
|
|
Ok.. with all said... What is the correct way to update a dialog from within a worker thread..
I've tried creating a refresh() function conected to the dialogs class.. Works fine from any CDialog:: classes.. but not from within workor thread.. I've tried pointers to the Cdialog function to get access from within the worker thread but UpdateData does not work..
Cstring variable;
CTestDlg * pointer = new CTestDlg
BOOL CMainDlg::OnInitDialog()
{
CDialog::OnInitDialog();
AfxBeginThread(workThread ,NULL);
return TRUE;
}
void CTestDlg::Refresh() //different dialog box
{
UpdateData(TRUE);
m_Listbox.SetWindowTextW(variable);
}
UINT workThread()
{
while(programisrunning){
variable = "text"; //<-- changeing variable throughout program actually..
pointer.Refresh();
}
}
This is pretty much the setup I need in my program.. I just can't get things workin no matter what I try...
Any Ideas.. ??
-- modified at 11:50 Monday 23rd October, 2006
|
|
|
|
|
aquawicket wrote: What is the correct way to update a dialog from within a worker thread..
By posting a message to the thread that owns the UI.
"Approved Workmen Are Not Ashamed" - 2 Timothy 2:15
"Judge not by the eye but by the heart." - Native American Proverb
|
|
|
|
|
Ok... Finally figured it out..
You are not suppose to make any calls to the dialog objects while in the worker thread.. Instead.. You must have your worker thread call another thread that will call it through a windows message Example using SendMessage()...
#define UWM_DOSOMETHING WM_APP+30 //define the User Windows Message
BEGIN_MESSAGE_MAP(CRetriggerDlg, CDialog)
ON_MESSAGE(UWM_RETRIGREFRESH, DoSomething) //Link the UWM to a class function
END_MESSAGE_MAP()
BOOL CMainDlg::OnInitDialog()
{
CDialog::OnInitDialog();
AfxBeginThread (WorkerThread, this); //call the worker thread
return TRUE;
}
UINT CMainDlg::WorkerThread( LPVOID pParam )
{
//Do your work in here
CMainDlg * me = (CMainDlg *)pParam;
me->MsgPump();
return 0;
}
void CMainDlg::MsgPump()
{
SendMessage(UWM_DOSOMETHING);
}
LRESULT CMainDlg::DoSomething(WPARAM, LPARAM)
{
//Play with the dialog window as you please here...
}
This snip of code should be enough to get anyone goin on using their working thread to talk to the dialog.. And yes.. UpdateData(); works just fine.. Just keep calls to the dialog outa the workerthread and the msgpump functions.. let DoSomething function do the talking..
-- modified at 19:19 Tuesday 24th October, 2006
|
|
|
|
|
How can I terminate this type of thread early?
|
|
|
|
|
Normally, you can call AfxEndThread() in the controlling function.
The MSDN documentation says: Call this function [AfxEndThread] to terminate the currently executing thread. Must be called from within the thread to be terminated. I hope this helps.
Enis Arif
|
|
|
|
|
Thanks for your help. In my case, I have I very long process working on my
worker thread, and I need to terminated the thread from the calling module.
Any idea how to do this ?
Thanks
|
|
|
|
|
You have to call AfxEndThread from the worker thread. If you have let's say a Stop button on your calling module, you can set a bool member var in your OnStop() method and check this var in your thread. If it is true then call AfxEndThread().
This should solve the problem, but be carefull what happens with the data your thread has already modified.
Enis Arif
|
|
|
|
|
|
//In .h of the class
///////////////////////
class CToto
{
// Implementation
protected:
struct SToto
{
BOOL m_bToto;
};
static UINT ThreadToto(LPVOID pParam);
};
//In .cpp of the class
////////////////////////
CToto::CToto()
{
SToto stToto;
SToto.m_bToto = TRUE;
AfxBeginThread(CToto::ThreadToto, this);
}
UINT CToto::ThreadToto(LPVOID pParam)
{
CToto* pToto = (CToto*)pParam;
pToto->SToto.m_bToto = FALSE;
return 0;
}
Enjoy!!!;)
|
|
|
|
|