gpt4 book ai didi

multithreading - 将消息从 Thread 发布到 GUI 最佳实践?

转载 作者:行者123 更新时间:2023-12-03 15:46:35 24 4
gpt4 key购买 nike

我正在开发小型监控应用程序,该应用程序将有一些线程通过 SNMP、TCP、ICMP 与某些设备进行通信,其他线程必须执行一些计算。所有这些结果我都必须在 GUI(某些表单或 TabSheets)中输出。

我正在考虑下一步的可能性:

  • 在每个工作线程中使用同步:
  • 使用共享缓冲区和Windows消息传递机制。线程会将消息放入共享缓冲区(队列)中,并用 Windows 消息通知 GUI。
  • 使用单独的线程来监听同步原语(事件、信号量等)并再次使用同步,但只能从 GUI 专用线程或 GUI 上的关键部分来显示消息。
  • 更新:(由一位同事提议)在主窗体中使用共享缓冲区和TTimer,它将定期检查(100-1000毫秒)共享缓冲区并消耗,而不是窗口消息传递。 (它比消息传递有什么好处吗?)
  • 其他?

尊敬的专家,请解释什么是最佳实践,或者公开替代方案的优点和缺点是什么。

更新:
作为想法:
//共享缓冲区+发送消息变体
LogEvent 全局函数将从任何地方调用(也从工作线程):

procedure LogEvent(S: String);
var
liEvent: IEventMsg;
begin
liEvent := TEventMsg.Create; //Interfaced object
with liEvent do
begin
Severity := llDebug;
EventType := 'General';
Source := 'Application';
Description := S;
end;
MainForm.AddEvent(liEvent); //Invoke main form directly
end;

在主窗体中,Events ListView 和共享部分(fEventList: TTInterfaceList 已经是线程安全的)我们将:

procedure TMainForm.AddEvent(aEvt: IEventMsg);
begin
fEventList.Add(aEvt);
PostMessage(Self.Handle, WM_EVENT_ADDED, 0, 0);
end;

消息处理程序:

procedure WMEventAdded(var Message: TMessage); message WM_EVENT_ADDED;
...
procedure TMainForm.WMEventAdded(var Message: TMessage);
var
liEvt: IEventMsg;
ListItem: TListItem;
begin
fEventList.Lock;
try
while fEventList.Count > 0 do
begin
liEvt := IEventMsg(fEventList.First);
fEventList.Delete(0);
with lvEvents do //TListView
begin
ListItem := Items.Add;
ListItem.Caption := SeverityNames[liEvt.Severity];
ListItem.SubItems.Add(DateTimeToStr(now));
ListItem.SubItems.Add(liEvt.EventType);
ListItem.SubItems.Add(liEvt.Source);
ListItem.SubItems.Add(liEvt.Description);
end;
end;
finally
fEventList.UnLock;
end;
end;

有什么不好的吗?主窗体在应用程序启动时分配一次,并在应用程序退出时销毁。

最佳答案

从每个工作线程使用同步

这可能是最简单的实现方法,但正如其他人指出的那样,这会导致 IO 线程被阻塞。这在您的特定应用程序中可能/可能不是问题。

但是应该注意的是,还有其他原因可以避免阻塞。阻塞可能会使性能分析变得更加棘手,因为它有效地增加了“匆忙等待”例程所花费的时间。

使用共享缓冲区和Windows消息传递机制

这是一个很好的方法,但有一些特殊的考虑。

如果您的数据非常小,PostMessage 可以将其全部打包到消息的参数中,使其成为理想选择。

但是,由于您提到了共享缓冲区,看来您可能有更多的数据。这是你必须小心一点的地方。从直观意义上使用“共享缓冲区”可能会让您面临竞争条件(但我稍后会更详细地介绍这一点)。

更好的方法是创建一个消息对象并将该对象的所有权传递给 GUI。

  • 创建一个新对象,其中包含 GUI 更新所需的所有详细信息。
  • 通过 PostMessage 中的附加参数传递对此对象的引用。
  • 当 GUI 处理完消息后,它负责销毁它。
  • 这巧妙地避免了竞争条件。
  • 警告:您需要确保 GUI 能够获取所有消息,否则将会出现内存泄漏。您必须检查 PostMessage 的返回值以确认它确实已发送,如果没有发送,您也可以销毁该对象。
  • 如果数据可以在轻量级对象中发送,则此方法非常有效。

使用单独的线程...

使用任何类型的单独中间线程仍然需要类似的考虑因素才能将相关数据获取到新线程 - 然后仍然必须以某种方式将其传递到 GUI。如果您的应用程序需要在更新 GUI 之前执行聚合和耗时的计算,这可能才有意义。就像您不想阻塞 IO 线程一样,您也不想阻塞 GUI 线程。

在主窗体中使用共享缓冲区和 TTimer

我前面提到了共享缓冲区的“直观想法”,意思是:“不同线程同时读写”;让您面临竞争条件的风险。如果在写入操作过程中开始读取数据,则可能会面临读取不一致状态数据的风险。这些问题对于调试来说可能是一场噩梦。

为了避免这些竞争条件,您需要依靠其他同步工具(例如锁)来保护共享数据。当然,锁让我们回到了阻塞问题,尽管形式稍微好一点。这是因为您可以控制所需保护的粒度。

这确实比消息传递有一些好处:

  • 如果您的数据结构庞大且复杂,您的消息可能效率低下。
  • 您无需定义严格的消息传递协议(protocol)来涵盖所有更新场景。
  • 消息传递方法可能会导致系统内出现数据重复,因为 GUI 会保留自己的数据副本以避免竞争情况。

有一种方法可以改进共享数据的理念,仅在适用时:某些情况下您可以选择使用不可变数据结构。也就是说:数据结构在创建后就不会改变。 (注意:前面提到的消息对象应该是不可变的。)这样做的好处是,您可以安全地读取数据(从任意数量的线程),而无需任何同步原语 - 前提是您可以保证数据不会更改。

关于multithreading - 将消息从 Thread 发布到 GUI 最佳实践?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20831009/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com