目标:主线程只需发命令(消息)通知子线程需要处理的内容,而不用管任务的处理进度,可以发任意个命令,而子线程依次取出主线程交付的任务来处理.
步骤:
1.主线程通过PostThreadMessage将命令消息发送给子线程,可以利用wParam与lParam传递需要的参数.
2.子线程执行代码中建立一个消息循环,PeekMessage从消息队列提取消息,若无消息,则用
MsgWaitForMultipleObjects等待消息或事件对象.
示例代码:
const
WM_TEST=WM_USER+1;
var
hCloseEvent:THandle;
ThreadID:DWORD;
type
TTestThread=class(TThread)
protected
procedure Execute;override;
end;
procedure TTestThread.Execute;
var
msg:TMsg;
begin
try
while True do
begin
if not PeekMessage(msg,0,0,0,PM_REMOVE) then
begin
case MsgWaitForMultipleObjects(1,hCloseEvent,False,INFINITE,QS_ALLINPUT) of
WAIT_OBJECT_0: Break;//hCloseEvent事件已经有效,故结束
WAIT_OBJECT_0+1:Continue;//消息队列中有消息存在
else Break;//其它情况,WM_FAILED或WAIT_ABANDONED_0,表明已经出错,故结束
end;
end else
begin
if WaitForSingleObject(hCloseEvent,0)<>WAIT_TIMEOUT then Break;
case msg.message of
WM_TEST:
begin
//...接到主线程发过来的命令,作相应处理
//...处理完之后,也可以用消息通知主线程
end;
end;
end;
end;
finally
//...作善后处理
end;
end;
procedure TForm1.btnCmdClick(Sender: TObject);
begin
//给子线程发送消息
PostThreadMessage(ThreadID,WM_TEST,0,0);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
//建立事件对象以及子线程,并保存子线程的ThreadID,用以调用PostThreadMessage
hCloseEvent:=CreateEvent(nil,True,False,0);
ThreadID:=TTestThread.Create(False).ThreadID;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
SetEvent(hCloseEvent);
CloseHandle(hCloseEvent);
end;
procedure TForm1.btnStopClick(Sender: TObject);
begin
SetEvent(hCloseEvent);
end;
可以参考”C# 对 Windows 窗体控件进行线程安全调用“一文来看。
C# 在子线程中创建不会阻塞执行的窗口
在做网络连接程序的时候碰到一个问题:每当连接到来时,都创建一个新的接收线程,而该接收线程在接收到消息时,可以创建一个新的对话窗口,而该窗口不能阻塞该接收线程的下一轮消息的接收,而且该接收线程还要把接收到的消息显示在该窗口上
Form.ShowDialog();方法弹出模态对话框,而模态对话框会阻塞后面代码的执行,导致接收线程无法继续执行(除非该模态窗口被关闭)
刚开始想到的解决办法,就是:通过Form.Show();方法,显示非模态的窗口
非模态对话框有几个问题:
(1) 生命期限问题:由于是在线程的一轮执行中创建的,所以,该对象可能在该轮结束后被垃圾回收机制自动清除掉;
(2) 模态对话框没有系统消息机制,需要自己写;(好像是这样的,没用过...)
(3) 在代码中尝试了一下,没有创建消息机制,只是显示窗口。但该窗口处于假死状态,根本没法用。
由于上述的几个困难,只能另觅它途了...
成功实验-----异步委托
在看其他文章的时候,偶然看到Control.Invoker()方法,该方法可执行自定义或系统定义的委托。
突然想起来,委托可以通过用Invoke()方法同步执行(阻塞执行),而通过BeginInvoke()方法异步执行,如果是异步执行的话,应该是不会阻塞线程的执行吧...
于是,做了如下的例子:
注意事项:
通过Invoke()/BeginInvoke()调用委托,必须由已经出现的控件来调用,否则会出现错误提示:“未经处理的异常: System.InvalidOperationException: 在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。”
(1) 打开窗口代码:
执行结果如下:
可见,新打开的模式窗口并没有阻塞线程的执行!
因为,如果是同步调用的话,在Form2窗口关闭之前,“新打开的窗口没有阻塞之后的执行”就不会有输出。