我使用Borland C ++ Builder 6.我有一个带有表单的应用程序。 app / main表单开始了一个主题。 (TThread
)线程创建服务器套接字的新实例,并侦听数据。当数据进入时,线程使用同步方法在主窗体上显示信息。
问题是,当线程发送信息时,如果单击主窗体上的菜单选项,则临时停止线程执行。如果我在同步方法中注释掉Form1->Memo1->Lines->Add(mStr)
,即线程没有向主表单发送信息,则线程继续执行而没有问题。因此,数据被接收并正确响应。
只要我恢复该行,并将数据写入主窗体,并在主窗体上选择了一个菜单选项,该线程就会暂时停止。有没有办法阻止这种行为,所以线程永远不会被“阻止”,但仍然可以向主表单报告?
在阅读了Martin的回复之后,我就是这样做的:
在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)
}
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);
}
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);
}
我还在PostMessage
函数中尝试了AddLog
。
一切正常,消息被写入备忘录,但当我点击主表单菜单时,应用程序仍然“冻结”。还有其他想法/帮助/例子吗?
感谢目前为止提供的所有帮助!
答案 0 :(得分:2)
大约25年前,我在使用Delphi TThread.Synchronize之前就已经看到了这一点。当我调查它是如何工作的时候,我停止使用Synchronize()并且之后没有使用过它(同样是TThread.waitFor和TThread.OnTerminate)。
使用PostMessage()。这比Synchronize()更省力,通常需要专用的'TthreadComms'类来承载数据,(在线程中创建,加载数据,在lParam中引用PostMessage,在用户定义的消息处理程序中回放,显示数据,免费)参考),但它仍然有效,同时弹出模态菜单选项等。
PostMessage()存在问题。有少量Windows操作可以在您的应用程序中重新创建窗口,因此更改表单句柄。如果消息直接发布到表单句柄(将消息发布到处理程序的最简单方法),操作系统可能会在操作期间重新创建窗口,这是一个很小但非零的可能性,因此更改窗口处理。这可能导致PostMessage在这个小窗口中失败。这可以避免,但这意味着更复杂。您可以使用RegisterClass()和CreateWindow()API创建一个不可见的窗口,并始终将您的线程消息发布到此窗口,在lParam中发送数据,在wParam中发送表单/ control reference 。在WndProc中,将wParam强制转换为TControl并调用TControl.Perform()以调用所需的消息处理程序。您只需在应用中使用其中一个隐形窗口。在Delphi中,将所有这些东西放在一个专用单元中并在窗口中创建初始化部分的内容相当容易 - 不确定C ++ Builder - 它是否有初始化部分?
使用PostMessage比较困难但是,与Synchronize()相比,无论主UI线程弹出了什么,不阻塞辅助线程,还没有重新设计三次尝试让它正常工作,是一个核心的Windows API,不会随时消失或改变 - 我在D3中编写的代码仍然可以在D2009中使用。
RGDS, 马丁
答案 1 :(得分:2)
SendMessage
阻塞,直到窗口过程实际处理消息。这样就可以解释与Synchronized
相同的行为。你真的应该使用PostMessage
(或其他东西,参见:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx)来异步完成它。
看起来您正在向关键部分内的TStringList
写一个字符串,但是当您从TStringList
读取(和删除)时,它不在关键部分。这可能会导致问题。您需要保护对该共享数据结构的所有访问权限。但是,这也会导致主线程和后台线程的相互阻塞。
为什么不摆脱关键部分,在AddLog
的堆上创建'log'字符串并使用消息的wParam
参数发送?
答案 2 :(得分:1)
我会创建一个简单的生产者/消费者队列,并让GUI线程在其空闲处理程序中轮询它。然后,当网络线程将某些内容放入其中时,它可以将虚拟消息发布到主线程。