1. 程式人生 > >多執行緒中觸發的事件函式在哪個執行緒中執行

多執行緒中觸發的事件函式在哪個執行緒中執行

轉載於:https://www.cnblogs.com/findumars/p/5289375.html

 在多執行緒開發中,如果在多執行緒中訪問主執行緒建立的物件,並觸發了這個物件的事件,將會執行這個事件的處理函式,那麼這個處理函式是在主執行緒中執行還是在觸發事件的執行緒中執行呢?針對這個問題做了一下測試,如果沒有通過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