在多线程开发中,如果在多线程中访问主线程创建的对象,并触发了这个对象的事件,将会执行这个事件的处理函数,那么这个处理函数是在主线程中执行还是在触发事件的线程中执行呢?针对这个问题做了一下测试,如果没有通过Windows消息触发事件,则在子线程(触发事件的线程)中执行事件处理函数,如果是由Windows消息触发的事件,则由主线程执行事件处理函数.这是因为Windows消息只由创建控件的线程进行处理,那么由此引起的事件及其处理函数自然就在创建控件的线程中执行了.而普通的事件触发,则全部在子线程中完成的,因此在子线程中执行事件处理函数.由此也解释了对于需要执行大量的任务的子线程,如果需要主线程显示处理进度,则可以在子线程中直接修改进度条控件的当前位置,主线程负责处理界面显示.这是因为子线程在修改进度条控件的当前位置时,会将一个Windows消息投递到消息队列,进度条的创建线程(主线程)在处理这个消息的时候,刷新界面上的进度.那么如果要在子线程中创建一个控件,并处理其由Windows消息触发的事件,消息会有子线程处理,子线程的消息队列会管理在本线程中创建的控件的消息.
验证1:普通的事件(无Windows消息触发)处理函数由子线程执行.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TNotify = procedure of object;
TCtrl = class
private
FNotify: TNotify;
public
property Notify: TNotify read FNotify write FNotify;
procedure TriggerNotify;
end;
TThrd = class(TThread)
private
FCtrl: TCtrl;
protected
procedure Execute; override;
public
constructor Create(ACtrl: TCtrl); reintroduce;
end;
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
FCtrl: TCtrl;
procedure Notify;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
OutputDebugString(PChar(Format('>>>>>:%d', [GetCurrentThreadId])));
FCtrl.TriggerNotify;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
OutputDebugString(PChar(Format('触发多线程的线程:%d', [GetCurrentThreadId])));
TThrd.Create(FCtrl);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FCtrl := TCtrl.Create;
FCtrl.Notify := Self.Notify;
end;
{ TCtrl }
procedure TCtrl.TriggerNotify;
begin
if Assigned(FNotify) then
FNotify;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FCtrl.Free;
end;
procedure TForm1.Notify;
begin
OutputDebugString(PChar(Format('>>>>>:%d', [GetCurrentThreadId])));
end;
{ TThrd }
constructor TThrd.Create(ACtrl: TCtrl);
begin
inherited Create(False);
FCtrl := ACtrl;
end;
procedure TThrd.Execute;
begin
FCtrl.TriggerNotify;
end;
end.
验证2:进度条处理当前位置变化的代码中是通过Windows消息通知创建控件的线程(主线程)的
procedure TProgressBar.SetPosition(Value: Integer);
begin
if not F32BitMode and ((Value < 0) or (Value > Limit16)) then
ProgressLimitError;
if HandleAllocated then SendMessage(Handle, PBM_SETPOS, Value, 0)
else FPosition := Value;
end;
验证3:可以在多线程中触发一个主线程创建的控件的事件,在处理函数中输出处理线程的ID,比较发现处理线程正是主线程.
验证4:在子线程中创建控件并处理由Windows消息触发的事件,事件由子线程执行.结论:子线程触发的控件事件,处理函数由创建控件的那个线程来执行.
结论:当一个线程第一次被创建时,系统假定线程不会用于任何与用户相关的任务.这样可以减少线程对系统资源的要求.但是,一旦该线程调用一个与图形用户界面有关的函数 ( 如检查它的消息队列或建立一个窗口 ),系统就会为该线程分配一些另外的资源,以便它能够执行与用户界面有关的任务.特别是,系统分配了一个THREADINFO结构,并将这个数据结构与线程联系起来.
另外管理控件与创建线程的关系是由Windows来完成的,将线程ID和控件的Handle进行映射,当需要向某个Handle发送消息时,会搜索其创建线程,并将消息投递到对应线程的消息队列中.
http://blog.csdn.net/henreash/article/details/7670420