【问题标题】:Thread execution "stops" when main form button is clicked单击主窗体按钮时线程执行“停止”
【发布时间】:2011-07-21 07:57:24
【问题描述】:

我使用 Borland C++Builder 6。我有一个带有表单的应用程序。应用程序/主窗体启动一个线程。 (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);
}

我还在AddLog 函数中尝试了PostMessage

一切正常,消息被写入备忘录,但当我点击主表单菜单时,应用程序仍然“冻结”。还有其他想法/帮助/示例吗?

感谢到目前为止的所有帮助!

【问题讨论】:

    标签: c++ multithreading c++builder


    【解决方案1】:

    大约 25 年前,我曾在 Delphi TThread.Synchronize 中看到过这种情况。当我调查它是如何工作的时,我停止使用 Synchronize() 并且从那以后就没有使用它(同样是 TThread.waitFor 和 TThread.OnTerminate)。

    使用 PostMessage()。这比 Synchronize() 更费力,通常需要专用的“TthreadComms”类来承载数据,(在线程中创建,加载数据,PostMessage 在 lParam 中引用,在用户定义的消息处理程序中回滚,显示数据,免费参考),但在弹出模式菜单选项等时它仍然有效。

    PostMessage() 存在问题。有少量 Windows 操作可以在您的应用程序中重新创建窗口,因此更改表单句柄。如果消息直接发布到表单句柄(将消息发布到处理程序的最简单方法),操作系统可能会在操作期间重新创建窗口的可能性很小但非零,因此更改 Window处理。这可能会导致 PostMessage 在这个小窗口内失败。这可以避免,但这意味着更加复杂。您可以使用 RegisterClass() 和 CreateWindow() API 创建一个不可见的窗口,并始终将线程消息发布到此窗口,在 lParam 中发送您的数据,在 wParam 中发送表单/控件reference。在 WndProc 中,将 wParam 转换为 TControl 并调用 TControl.Perform() 以调用所需的消息处理程序。在您的应用程序中,您只需要其中一个不可见的窗口。在 Delphi 中,将所有这些东西放在一个专用单元中并在初始化部分中创建窗口是相当容易的 - 不确定 C++ Builder - 它有初始化部分吗?

    PostMessage 使用起来比较困难,但与 Synchronize() 相比,无论主 UI 线程弹出什么,它都能可靠地工作,不会阻塞辅助线程,没有重新设计 3 次尝试使其正常工作,并且是一个不会消失或随时更改的核心 Windows API - 我在 D3 中编写的代码在 D2009 中仍然有效。

    Rgds, 马丁

    【讨论】:

    • 您可以使用AllocateHWnd() 来创建隐藏窗口,而不是直接调用CreateWindow(),或者您可以使用TApplication 窗口并为其OnMessage 事件分配一个处理程序。出于您所说的原因(娱乐),不建议将PostMessage() 用于控件的窗口,但是与您未提及的相关的还有更危险的副作用。不,C++ 没有初始化部分,但是可以使用#pragma startup 或全局类实例来模拟它。
    【解决方案2】:

    SendMessage 阻塞,直到窗口过程实际处理该消息。所以这可以解释与Synchronized 相同的行为。你真的应该使用PostMessage(或其他东西,请参阅:http://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx)来异步完成它。

    看起来您正在将单个字符串写入关键部分内的TStringList,但是当您从该TStringList 读取(和删除)时,它不在关键部分中。这可能会导致问题。您需要保护对该共享数据结构的所有访问。但是,这也会导致主线程和后台线程相互阻塞。

    为什么不去掉临界区,在AddLog 的堆上创建“日志”字符串并使用消息的wParam 参数发送?

    【讨论】:

      【解决方案3】:

      我将创建一个简单的生产者/消费者队列,并让 GUI 线程在其空闲处理程序中轮询它。然后,当网络线程向其中放入一些东西时,它可以向主线程发送一条虚拟消息。

      【讨论】:

      • 好吧,如果您要在空闲处理程序中不断地轮询线程安全队列,那实际上是浪费了一个内核。如果要发布一条消息来告诉主线程获取一些数据,如果您可以将数据放入消息中,为什么还要麻烦额外的队列?
      • @Martin James:我不是这个意思。微软的 MFC 有这样一个 OnIdle 处理程序(大部分)来更新菜单项和工具栏按钮的灰色/非灰色状态。每次事件队列变空并使用很少的 CPU 时间时都会触发它。我很久以前就在 Borland 的 VCL 中使用过类似的东西。不过不记得我是怎么做到的了。
      • 我查了一下——有一个名为 ApplicationEvents 的组件,它有一个 Idle 事件可以用于这种事情。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-23
      • 1970-01-01
      • 1970-01-01
      • 2015-04-25
      相关资源
      最近更新 更多