DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: jeff1314
今日帖子: 10
在线用户: 10
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/11/29 14:38:09
标题:
[求助]关于多线程中线程通信事件问题 浏览:1984
加入我的收藏
楼主: 我习惯采用事件方式通信,与大部多线程文档采用SendMessage/PostMessage方式不同,但在实际过程中,多线程时好像以下代码会出现问题。其实也不知道用这个标题是否合适,自己也是很

晕,直接上代码好了。

----------
定义一个类:
unit xxx
...
type
  TMsgCallBack = procedure(AMsg: string) of object;

  TMyObj = class(Tobject)
  private
    FLock: TMultiReadExclusiveWriteSynchronizer;
    FSL: TStringList;
    FMsgCallBack: TMsgCallBack;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetName(const AName: string);
    function GetName: string;
  published
    property OnMsgCallBack: TMsgCallBack read FMsgCallBack write FMsgCallBack;
end;

implementation

constructor TMyObj.Create;
begin
  inherited Create;
  FLock := TMultiReadExclusiveWriteSynchronizer.Create;
  FSL := TStringList.Create;
  FMsgCallBack := nil;
end;

destructor TMyObj.Destroy;
begin
  FreeAndNil(FSL);
  FLock.Free;
  inherited;
end;

procedure SetName(const AName: string);
begin
  Lock.BeginWrite;
  try
    FSL.Value[aaa] := AName;
  finally
    Lock.EndWrite; 
  end; 
  
  if Assign(FMsgCallBack) then FMsgCallBack('已经设置:' + AName);
end;
 
function GetName: string;
begin
  Lock.BeginRead;
  try
    Result := List.Values['aaa'];
  finally
    Lock.EndRead; 
  end;

  if Assign(FMsgCallBack) then FMsgCallBack('已经读取:' + Result);
end;

----------
FMyObj: TMyObj; 
FMyObj实例自创建,销毁时 Free,不上代码了。
----------

主界面中引用该类和实例:
use xxx
 
type
  TMainForm = class(TForm)
...
  protected
    procedure OnMsgCallBack(AMsg: string);
end; 

procedure TMainForm.OnMsgCallBack(AMsg: string);
begin
  //事件触发时将AMsg信息加入到窗体Listview中
  FItem := ListView1.Items.add;
  FItem.Caption := AMsg;
  FItem.SubItems.Add(FormatDateTime(yyyy-mm-dd hh:nn:ss:zzz));
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FMyObj.OnMsgCallBack := Self.OnMsgCallBack;  
end;

----------
多线程由其他第三方控件创建、管理,线程中访问自定义TMyObj类的
FMyObj实例。并使用实例中SetName和GetName过程和函数,通过事件
回调信息到主窗体的Listview中记录事件。
----------

现在的问题是有时候会卡死程序。该写法参考源码FDManage,请教以上这种写法有什么问题?
----------------------------------------------
-
作者:
男 bahamut8348 (leonna) ★☆☆☆☆ -
普通会员
2019/11/29 15:53:24
1楼: const
  cwm_cb = wm_user + $0010;

type
tmainform = class...
private
  procedure domsgcb(var msg: tmessage); message cwm_cb;
protected
  procedure onmsgcallback(msg: string);
...
end;

procedure tmainform.domsgcb(...);
var
  s: string;
begin
  s := pchar(msg.wparam);
  //事件触发时将AMsg信息加入到窗体Listview中
  FItem := ListView1.Items.add;
  FItem.Caption := s;
  FItem.SubItems.Add(FormatDateTime(yyyy-mm-dd hh:nn:ss:zzz));
end;

procedure TMainForm.OnMsgCallBack(AMsg: string);
begin
  sendmessage(handle, cwm_cb, wparam(pchar(amsg)), 0);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FMyObj.OnMsgCallBack := Self.OnMsgCallBack;  
end;
----------------------------------------------
--
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2019/11/29 16:58:01
2楼: 大部多线程文档采用SendMessage/PostMessage方式
==========
可能是我水平低,从来没听说过,也没见过这种。、

我只知道 回调模式,新版本 可以使用 匿名回调函数。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/11/29 18:02:28
3楼: @bahamut8348 (leonna)
你是增加自定义消息,在回调事件中再SendMessage一次?为什么?
回调信息都已经在主线程了,再SendMessage给自己。。。。不懂

能告诉我原理吗?

@wang_80919
猫哥,你就别说反话了,我就是不明白为什么这样写法,也会导致主线程卡死。
至于线程间通信,百度一搜,都是采用SendMessage方式的。

请各位熟悉的,告知一下。我的代码是否存在什么问题?
----------------------------------------------
-
作者:
男 bahamut8348 (leonna) ★☆☆☆☆ -
普通会员
2019/11/29 18:32:02
4楼: 谁说的回调一定在主线程的?createthread就有一个参数是回调函数,如果回调一定是主线程的话,那还怎么创建子线程的block?
用getcurrentthreadid一试就知道。

sendmessage,postmessage才会通过消息循环把信息传递到主线程里执行。
----------------------------------------------
--
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/11/29 20:03:53
5楼: @bahamut8348
我上面的代码里面,难道事件回调不在主线程吗?
----------------------------------------------
-
作者:
男 helyna (Person) ★☆☆☆☆ -
普通会员
2019/11/29 22:30:08
6楼: 如果是在线程中调用FMyObj的函数,卡会不会因为ListView1不是多线程安全的原因?
回调函数是否在主线程,不是看你代码写在哪里,是看调用方是不是在主线程。
----------------------------------------------
-
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/11/30 19:53:10
7楼: 回调是线程回调,也就是在调用线程里

访问UI的代码需要放到主线程里

TThread.Synchronize
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2019/11/30 22:06:00
8楼: 楼上正解。用楼上的办法,完全不需要 SendMesasage。
所以,百度的答案都是错误的。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/12/1 10:18:14
9楼: @codecoolie @wang_80919
我也知道线程访问UI需要用 TThread.Synchronize 进行同步,但是线程并不是我自己开的,是第三方控件自己生成和管理,只需要打开一个Multithread = True;

如果按这样说法,是不是可以在主线程这样改:
procedure TMainForm.OnMsgCallBack(AMsg: string);
  ...
begin
  TThread.Synchronize(nil)
   procedure
     begin
       //事件触发时将AMsg信息加入到窗体Listview中
       FItem := ListView1.Items.add;
       FItem.Caption := AMsg;
       FItem.SubItems.Add(FormatDateTime(yyyy-mm-dd hh:nn:ss:zzz));
    end);
  end);
end;

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FMyObj.OnMsgCallBack := Self.OnMsgCallBack;  
end;

另外 FSL: TStringList 现在是放入字符串而已,如果使用AddObject放入的是对象,在多线程的时候是 Lock FSL.Objects[x] 锁定内部单一对象,还是需要锁定整个FSL。
----------------------------------------------
-
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/12/1 20:51:02
10楼: 这样可以,不过不是“在主线程这样改”,你的Self.OnMsgCallBack只是“写在你的代码里的函数/Method而已”,回调是在调用者线程里执行(可以是主线程,也可以是非主线程),Synchronize的代码在主线程里执行
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/12/1 22:41:03
11楼: @codecoolie
原来是封装FFMPEG的大神,感谢您的回复。

我大概明白你的意思了:谁调用回调谁去同步(多线程而言)
以我的例子来说,多线程是第三方控件创建的,那样Sychronize就应该是它内部的线程来同步这个回调。

另外关于这个问题,能否再赐教一下:
FSL: TStringList 现在是放入字符串而已,如果使用AddObject放入的是对象,在多线程的时候是 Lock FSL.Objects[x] 锁定内部单一对象,还是需要锁定整个FSL。
----------------------------------------------
-
作者:
男 138soft (138soft) ★☆☆☆☆ -
盒子活跃会员
2019/12/1 22:43:54
12楼: 实际上在多线程里面使用send/postmessage,会导致线程暂停,效率并不高。感兴趣的可以写个程序实际上测试一下。
----------------------------------------------
是你上错了车,还是我下错了站?
作者:
男 bahamut8348 (leonna) ★☆☆☆☆ -
普通会员
2019/12/2 13:48:54
13楼: 楼上说send/postmessage会导致线程暂停?估计是没懂send和post的区别,也没有仔细去看vcl底层是怎么实现synchronize的,只是信口胡说了一下罢了。

简单来说:
sendmessage会直接发送消息给指定窗口,等待窗口处理完毕再继续执行,这个过程线程是会被挂起的。
postmessage则是发送一个消息到指定窗口消息队列的尾部,并且立即返回,这个过程并不会造成线程挂起现象。
而synchronize则是在vcl内部有一个列表去存储需要执行的proc或者说是block,然后在application的WakeMainThread里通过postmessage发送一个wm_null消息给主线程,最后再在主线程消息循环里处理wm_null的时候通过CheckSynchronize来执行存储的proc或者是block。

如果说postmessage都卡顿,那么synchronize一定卡的飞起信不?

当然,线程同步本身就会影响效率,如果说的是由于同步导致的效率低下,那就不要同步。不过不同步的线程处理起来需要一些技巧。估计彻底异步的线程可能才更符合当初设计的意图吧。
----------------------------------------------
--
作者:
男 hs_kill (lzl_17948876) ★☆☆☆☆ -
普通会员
2019/12/2 14:30:11
14楼: postmessage本质来说也是要阻塞同步的, 只不过开销很小, 感觉不出来而已
而synchronize开销就比较大了, 要等你自己的代码都执行完了才行

synchronize或者信号什么的, 一般用于线程处理完成吧结果同步给主进程用, 此时没有需要异步处理的东西了, 所以阻塞也无所谓

简单的数据传递用postmessage 申请个内存吧数据放进去post出去就完全不用管了, 一般通知啊, 瞬时状态什么的用这个比较省事

sendmessage, 在线程里用了没什么优点, 不如用synchronize
----------------------------------------------
http://www.cnblogs.com/lzl_17948876/
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2019/12/2 15:00:53
15楼: 楼上是正解。
EMB 的 安卓 IOS 下面,也搞了一套 高仿 WIN 的 消息机制,大部分是 PostMessage 模式。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/12/2 20:59:16
16楼: 回楼主:

我大概明白你的意思了:谁调用回调谁去同步(多线程而言)
以我的例子来说,多线程是第三方控件创建的,那样Sychronize就应该是它内部的线程来同步这个回调。

你这说法还是不对,是“谁调用就在谁的线程中执行”,不是第三方Sychronize

你研究下Delphi源码就知道了

其实我学习Delphi的方式基本上只有一条,就是看Delphi的源码。

TStringList的问题,我不明白你意思,可能你表达有误,不过你也可以通过研究Delphi源码了解。

多线程中可以使用TThreadList
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/12/2 21:05:12
17楼: 另外,多线程通信使用SendMessage/PostMessage说法还真不了解,我一般都是根据具体需求使用“临界区、互斥、信号量、事件”的一种或几种配合,但此“事件”非Delphi的“事件”,Delphi的事件本质上就是回调函数而已。
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/12/2 21:20:19
18楼: Delphi的Sychronize机制,也可以研究下Delphi源码了解,本质上就如bahamut8348所说,也是PostMessage方式。

这个同步机制的限制是,需要程序里有Application实例才行。

如果用Delphi写的动态库里要用到TThread.Sychronize机制,则需要自己实现WakeMainThread通知事件,以及窗口过程WndProc,之后在自己实现的WakeMainThread事件中,通过PostMessage把同步请求通知窗口过程,窗口过程收到消息后调用CheckSynchronize,最终实现主线程同步。
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
男 pcplayer (pcplayer) ★☆☆☆☆ -
普通会员
2019/12/2 23:55:16
19楼: 楼主你代码太长懒得看了。看你的描述,线程事件通知外面。看底下的回帖,似乎就是你没搞清楚线程回调和主线程是啥关系。

这就是我常常说的,写多线程的程序其实很简单,网上说得很复杂很吓人,其实就是程序员没搞清楚一个事情:你写的一个函数,一个方法,究竟是被主线程调用,还是被其它什么线程调用?尤其是代码架构复杂了,调用层次深了的时候。

比如,你在一个单元,一个类里面,实现了一个方法A;在另外一个单元,另外一个类里面,实现了一个线程B。然后,线程B里面有事件,调用到方法 A,那你必须知道,这个方法A是被线程调用的。如果方法A里面有一些涉及到线程的事,比如锁,临界区,或者 UI 的变化,那么,你就要注意了。

清楚这一点,线程的问题都很容易解决。当然要知道一些基本概念,比如UI的变化,比如 Label1.Caption := AAA; 这种代码被调用,不能是线程,只能让主线程去调用它。但假设包含这样的代码的方法会被线程调用到,怎么办?加上同步嘛。


以前D7的年代,加同步很麻烦,似乎只能在线程里面加。那就在线程代码的调用事件的地方,加上同步。也就是用同步方法去调用事件。

现在的DELPHI可以在被调用的函数那里加,直接 TThread.Syn.... 就可以同步了。

如果你深入去看同步方法的代码,其实就是线程停下来,发个消息给主线程,让主线程去执行需要同步的方法,主线程执行完了,返回,线程继续往下执行。当然我说的这个是 WINDOWS 底下利用 WINDOWS 消息机制 D7 的代码。现在新版的多平台的代码是怎么做的,俺还没深入看。
----------------------------------------------
-
作者:
男 bahamut8348 (leonna) ★☆☆☆☆ -
普通会员
2019/12/3 1:59:56
20楼: 严格来说,多线程、多进程同步一般使用的手段是“临界区”、“互斥对象”、“事件”、“信号量”,sendmessage和postmessage由于会发送消息到主线程也同样有同步的效果。

不过我没有看到具体的实现代码,但是我对这个“postmessage也要阻塞同步”的说法持保留意见,实际上postmessage的原理只是丢一个消息在队列的最后面而已。我想如果要做的话,实现windows内核的那些coder实现一个无锁队列应该不是难事。当然这不是问题的重点。问题的重点是:sendmessage的优点是让你做到谁申请的资源谁回收,如果用postmessage的话只能在子线程里申请资源,在主线程里回收资源,这就好比是自己拉的大便让别人去扫一样。

回到sendmessage和postmessage的问题上来说,windows系统并不建议在子线程里去写ui相关的控制代码,要把相关代码推到主线程来做,最简单的方式就是postmessage。
----------------------------------------------
--
作者:
男 nickemma (N.E Zhou) ★☆☆☆☆ -
普通会员
2019/12/4 11:47:28
21楼: 感谢各位大神的教导,再次感悟回调事件的工作方式,就此结贴。

@bahamut8348
对不起,现在明白了您SendMessage的意义了。

之前我认为:FMyObj.OnMsgCallBack := Self{MainForm}.OnMsgCallBack; 回调就肯定会在MainForem的主线程中,现经过 codecoolie 、pcplayer 解释,明白 Self{MainForm}.OnMsgCallBack; 只是一个写在MainForm的函数或方法,视乎谁去调用。

意思说如果该方法/函数,由多线程访问,并且会访问UI,就必须进行线程同步。
【自己再加多一句:如果多线程访问公用变量、对象,就必须加锁】
至于同步的方法,可以用 TThread.Synchronize 、SendMessage和PostMessage等
----------------------------------------------
-
作者:
男 helyna (Person) ★☆☆☆☆ -
普通会员
2019/12/4 12:50:23
22楼: @nickemma

“至于同步的方法,可以用 TThread.Synchronize 、SendMessage和PostMessage等”
----------
不做任何处理的话,简单的调用PostMessage并不能做到同步,PostMessage是异步函数。PostMessage就像邮递员,只负责把信件放进邮箱,不去确认收信人是否取到,
TThread.Synchronize之所以能做到同步,是因为Delphi内部有处理,会等事件处理完毕后才继续执行。SendMessage也能做到同步。
----------------------------------------------
-
作者:
男 codecoolie (CodeCoolie) ★☆☆☆☆ -
普通会员
2019/12/4 14:20:04
23楼: pcplayer “以前D7的年代,加同步很麻烦,似乎只能在线程里面加。”

说法有误。

Delphi 7 是直接支持 class procedure Synchronize 的。

Delphi 6 不支持 class procedure Synchronize,需要实例化的线程。但是不是必须用自己的线程,任意实例化一个空线程即可。

Delphi 5 以及之前没研究过,因为 Delphi 是从 6 开始跟 C 语言的某些特性兼容的,也就是 Kylix 同期版本的时代。
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行95.70313毫秒 RSS