导航:
论坛 -> DELPHI技术
斑竹:liumazi,sephil
作者:
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,请教以上这种写法有什么问题?
----------------------------------------------
-
作者:
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;
----------------------------------------------
--
作者:
2019/11/29 16:58:01
2楼:
大部多线程文档采用SendMessage/PostMessage方式 ========== 可能是我水平低,从来没听说过,也没见过这种。、 我只知道 回调模式,新版本 可以使用 匿名回调函数。
----------------------------------------------
(C)(P)Flying Wang
作者:
2019/11/29 18:02:28
3楼:
@bahamut8348 (leonna) 你是增加自定义消息,在回调事件中再SendMessage一次?为什么? 回调信息都已经在主线程了,再SendMessage给自己。。。。不懂 能告诉我原理吗? @wang_80919 猫哥,你就别说反话了,我就是不明白为什么这样写法,也会导致主线程卡死。 至于线程间通信,百度一搜,都是采用SendMessage方式的。 请各位熟悉的,告知一下。我的代码是否存在什么问题?
----------------------------------------------
-
作者:
2019/11/29 18:32:02
4楼:
谁说的回调一定在主线程的?createthread就有一个参数是回调函数,如果回调一定是主线程的话,那还怎么创建子线程的block? 用getcurrentthreadid一试就知道。 sendmessage,postmessage才会通过消息循环把信息传递到主线程里执行。
----------------------------------------------
--
作者:
2019/11/29 20:03:53
5楼:
@bahamut8348 我上面的代码里面,难道事件回调不在主线程吗?
----------------------------------------------
-
作者:
2019/11/29 22:30:08
6楼:
如果是在线程中调用FMyObj的函数,卡会不会因为ListView1不是多线程安全的原因? 回调函数是否在主线程,不是看你代码写在哪里,是看调用方是不是在主线程。
----------------------------------------------
-
作者:
2019/11/30 22:06:00
8楼:
楼上正解。用楼上的办法,完全不需要 SendMesasage。 所以,百度的答案都是错误的。
----------------------------------------------
(C)(P)Flying Wang
作者:
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。
----------------------------------------------
-
作者:
2019/12/1 20:51:02
10楼:
这样可以,不过不是“在主线程这样改”,你的Self.OnMsgCallBack只是“写在你的代码里的函数/Method而已”,回调是在调用者线程里执行(可以是主线程,也可以是非主线程),Synchronize的代码在主线程里执行
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
2019/12/1 22:41:03
11楼:
@codecoolie 原来是封装FFMPEG的大神,感谢您的回复。 我大概明白你的意思了:谁调用回调谁去同步(多线程而言) 以我的例子来说,多线程是第三方控件创建的,那样Sychronize就应该是它内部的线程来同步这个回调。 另外关于这个问题,能否再赐教一下: FSL: TStringList 现在是放入字符串而已,如果使用AddObject放入的是对象,在多线程的时候是 Lock FSL.Objects[x] 锁定内部单一对象,还是需要锁定整个FSL。
----------------------------------------------
-
作者:
2019/12/1 22:43:54
12楼:
实际上在多线程里面使用send/postmessage,会导致线程暂停,效率并不高。感兴趣的可以写个程序实际上测试一下。
----------------------------------------------
是你上错了车,还是我下错了站?
作者:
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/
作者:
2019/12/2 15:00:53
15楼:
楼上是正解。 EMB 的 安卓 IOS 下面,也搞了一套 高仿 WIN 的 消息机制,大部分是 PostMessage 模式。
----------------------------------------------
(C)(P)Flying Wang
作者:
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
作者:
2019/12/2 21:05:12
17楼:
另外,多线程通信使用SendMessage/PostMessage说法还真不了解,我一般都是根据具体需求使用“临界区、互斥、信号量、事件”的一种或几种配合,但此“事件”非Delphi的“事件”,Delphi的事件本质上就是回调函数而已。
----------------------------------------------
FFmpeg for Delphi http://www.CCAVC.com http://www.DelphiFFmpeg.com
作者:
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
作者:
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 的代码。现在新版的多平台的代码是怎么做的,俺还没深入看。
----------------------------------------------
-
作者:
2019/12/3 1:59:56
20楼:
严格来说,多线程、多进程同步一般使用的手段是“临界区”、“互斥对象”、“事件”、“信号量”,sendmessage和postmessage由于会发送消息到主线程也同样有同步的效果。 不过我没有看到具体的实现代码,但是我对这个“postmessage也要阻塞同步”的说法持保留意见,实际上postmessage的原理只是丢一个消息在队列的最后面而已。我想如果要做的话,实现windows内核的那些coder实现一个无锁队列应该不是难事。当然这不是问题的重点。问题的重点是:sendmessage的优点是让你做到谁申请的资源谁回收,如果用postmessage的话只能在子线程里申请资源,在主线程里回收资源,这就好比是自己拉的大便让别人去扫一样。 回到sendmessage和postmessage的问题上来说,windows系统并不建议在子线程里去写ui相关的控制代码,要把相关代码推到主线程来做,最简单的方式就是postmessage。
----------------------------------------------
--
作者:
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等
----------------------------------------------
-
作者:
2019/12/4 12:50:23
22楼:
@nickemma “至于同步的方法,可以用 TThread.Synchronize 、SendMessage和PostMessage等” ---------- 不做任何处理的话,简单的调用PostMessage并不能做到同步,PostMessage是异步函数。PostMessage就像邮递员,只负责把信件放进邮箱,不去确认收信人是否取到, TThread.Synchronize之所以能做到同步,是因为Delphi内部有处理,会等事件处理完毕后才继续执行。SendMessage也能做到同步。
----------------------------------------------
-
作者:
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