Thread execution "stops" when main form button is clicked
I use Borland C++Builder 6. I have an app, with a form. The app/main form kicks off a thread. (TThread
) Thread creates a new instance of server socket, and listens for data. When data comes in, thread displays info on main form using synchronize method.
Problem is, while thread sends info, if a menu option on the main form is clicked, thread execution is temporary halted. If I comment out the Form1->Memo1->Lines->Add(mStr)
in the synchronize method, i.e. the thread is not sending info to the main form, the thread continues executing without a problem. So data is received and responded to correctly.
As soon as I restore the line, and write the data to the main form, and a menu option is selected on the main form, the thread is temporary halted. Is there a way to stop this behaviour so the thread is never "blocked", but still can report to the main form?
After reading Martin's response, here is what I did:
in Main.h:
#define WM_ADDLOG (WM_USER+0x0500)
class TForm1: public TForm
{
...
private:
void __fastcall virtual HandleAddLog(TMessage &msg);
...
public:
HWND hWnd;
TStringList *FStringBuf;
__property TStringList *StringBuf={read=FStringBuf,write=FStringBuf};
TCriticalSection tcsMsg;
...
protected:
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_ADDLOG,TMessage,HandleAddLog)
END_MESSAGE_MAP(TForm)
}
in Main.cpp:
In TForm1 constructor:
...
hWnd=FindWindow(NULL,"SoftIEN");
if(!hWnd)
{
exit(0);
}
FStringBuf = new TStringList;
tcsMsg = new TCriticalSection;
...
void __fastcall TForm1::HandleAddLog(TMessage &msg)
{
String strN,strDateTime,strLine;
if(Memo1->Lines->Count>10000)
Memo1->Lines->Clear();
while(FStringBuf->Count)
{
strDateTime = "";
DateTimeToString(strDateTime, "yy/mm/dd hh:nn:ss.zzz: ", Now());
strN=FStringBuf->Strings[0];
FStringBuf->Delete(0);
strLine=strDateTime + strN;
Memo1->Lines->Add(strLine开发者_如何学Go);
}
TForm::Dispatch(&msg);
}
In Thread.cpp
...
m_strMsg="Some Message";
AddLog();
...
void __fastcall TIENServerThread::AddLog()
{
Form1->tcsMsg->Acquire();
Form1->StringBuf->Add(m_strMsg);
Form1->tcsMsg->Release();
SendMessage(Form1->hWnd,WM_ADDLOG,0, 0);
}
I also tried PostMessage
in my AddLog
function.
Everything works fine, the messages are written to the Memo, but the app still "freezes" when I click on the main form menu. Any other ideas/help/examples?
Thanks for all the help so far!
I've seen this before with Delphi TThread.Synchronize, about 25 years ago. When I investigated to see how it worked, I stopped using Synchronize() and haven't used it since, (likewise TThread.waitFor and TThread.OnTerminate).
Use PostMessage(). This is more effort than Synchronize(), often requiring a dedicated 'TthreadComms' class to carry the data, (create in thread, load data, PostMessage the reference in lParam, cast back in user-defined message-handler, display data, free reference), but it still works while modal menu options etc. are popped up.
There is an issue with PostMessage(). There are a small number of Windows operations that can recreate windows in your app, so changing the form handle. If the message is posted directly to a form handle, (the easiest way to post the message to the handler), there is a small, but non-zero, possibility that the OS may recreate the window during the operation, so changing the Window handle. This could result in a PostMessage fail in this small window of time. This can be avoided, but it mean yet more complication. You can create an invisible window with the RegisterClass() and CreateWindow() API's and always post your thread messages to this window, sending your data in lParam and the form/control reference in wParam. In the WndProc, cast wParam to a TControl and call TControl.Perform() to call the desired message-handler. You only need one of these invisible windows in your app. In Delphi, it's fairly easy to just put all this stuff in a dedicated unit and do the window create stuff in an initialization section - not sure about C++ Builder - does it have initialization sections?
It is more difficult to use PostMessage but, in contrast to Synchronize(), it works reliably no matter what the main UI thread has popped up, does not block the secondary thread, has not been redesigned three times in an attempt to make it work properly and is a core windows API that is not going away or changing anytime ever - the code I wrote in D3 still works in D2009.
Rgds, Martin
SendMessage
blocks until the window procedure actually processes the message. So that would explain the same behaviour as with Synchronized
. You really should use PostMessage
(or something else, see: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx) to get it done asynchronously.
It looks like you are writing a single string to a TStringList
inside a critical section, but when you are reading (and deleting) from that TStringList
it's not in a critical section. This will probably lead to problems. You need to protect all access to that shared data structure. However, that would also lead mutual blocking of the main and background thread.
Why don't you get rid of the critical section, create the 'log' string on the heap in AddLog
and send that using the wParam
parameter of the message?
I would create a simple producer/consumer queue and let the GUI thread poll it in its idle handler. Then, when the network thread puts something into it, it can just post a dummy message to the main thread.
精彩评论