2009-11-14 575 views
9

我需要創建在Delphi一個線程具有以下特徵:Delphi的線程等待數據,對其進行處理,然後繼續等待

  • 等待,直到主線程將數據添加到共享隊列。
  • 處理隊列中的所有數據,並將結果返回主線程(對於最後一部分,我將發送消息到主窗口)。處理非常耗時,因此可能會在工作線程正在處理以前的條目時將新數據添加到隊列中。
  • 繼續等待,使用盡可能少的cpu週期。

我不能發送消息給線程,因爲它沒有窗口句柄。

我應該使用WaitForObject的一些變體嗎?如果是這樣,等待什麼?如果沒有,那我該如何保持線程在等待,然後在新數據進入隊列時喚醒它?

我讀過Multithreading - The Delphi Way,這似乎沒有回答我的問題。也許OmniThreadLibrary可以做我需要的;由於文檔很少,我無法分辨。我對通常的線程知之甚少,不知道圖書館是否會在這裏提供幫助以及如何使用它(或者甚至爲什麼要使用它而不是僅僅使用TThread後代)。

+2

你寫的:「我讀過多線程 - 德爾菲方式,這似乎並沒有回答我的問題。」但它在第9章中有介紹。生產者 - 消費者 - 關係就是你要找的東西,而信號量的確是實現這種排隊的一種方式。 – mghie 2009-11-14 21:37:49

回答

13

OmniThreadLibrary絕對可以幫到你。 OTL發行版中的測試5可以幫助您開始。

在這個演示中,「開始」按鈕創建線程並設置一些參數和計時器(如果不需要,可以在代碼中將其刪除)。 「更改消息」向該線程發送消息,並且該消息在線程的OMChangeMes​​sage方法中處理。線程然後將一些信息發送回客戶端(本演示中的OMSendMessage,但是您可以在您要完成的同一消息中執行此操作),並且主線程通過OmniEventMonitor組件收到此消息。 「停止」按鈕停止工作線程。

如果在線程繁忙時收到更多消息,只要您的工作方法完成其工作,它們就會排隊並處理。當沒有什麼可做的時候,線程將在進程中使用零CPU週期等待下一條消息。

EDIT

在2009 Delphi和以上時,Background Worker模式提供了一個簡單的解決方案。

+2

OmniThreadLibrary有,只要它是更好地記錄洙多潛能...... – Remko 2009-11-15 22:23:19

+0

是的,我知道,我知道... :( 文檔是1.04版本後,名單上的第一件事情 – gabr 2009-11-16 07:13:23

+1

文檔增長緩慢:HTTP:/ /www.leanpub.com/omnithreadlibrary – gabr 2012-11-06 22:27:58

1

即使沒有窗口句柄,也可以將消息明確地發送給線程。只需使用PostThreadMessage()而不是SendMessage()PostMessage()。如果你在[delphi]標籤中搜索PostThreadMessage(),StackOverflow會有更多的信息 - 我認爲在這裏複製所有內容不是一個好主意。

但是,如果你不懂線程編程,那麼從OTL開始而不是低層次的東西可能確實是一件好事。

2

WaitForSingleObject()可以等待幾種類型的同步對象。您可以使用Windows「事件」同步對象(與Delphi事件無關)。您創建事件(SyncObjs中有一個Delphi TEvent包裝器,IIRC),並調用WaitForSingleObject等待該事件發出信號。當你不得不喚醒線程時,你調用SetEvent把事件置於信號狀態,WaitForSingleObject返回。您可以使用WaitForMultipleObjects()等待一個(或全部)多個對象的線程 - 它也會告訴您哪個對象變成了信號。

1

這裏有一個簡單的例子,你如何能做到這一點...

const 
    WM_MY_RESULT = WM_USER + $1; 

type 
    TMyThread = class(TThread) 
    private 
    FKilled: Boolean; 
    FListLock: TRTLCriticalSection; 
    FList: TList; 
    FJobAdded: TEvent; 
    protected 
    procedure Execute; override; 
    procedure DoJob(AJob: Integer); 
    public 
    constructor Create(CreateSuspended: Boolean); 
    destructor Destroy; override; 
    procedure Kill; 
    procedure PushJob(AJob: Integer); 
    function JobCount: Integer; 
    function GetJob: Integer; 
    end; 


    TThreadingForm = class(TForm) 
    lstResults: TListBox; 
    se: TSpinEdit; 
    btn: TButton; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure btnClick(Sender: TObject); 
    private 
    FThread: TMyThread; 
    procedure OnMyResultMessage(var Msg: TMessage); message WM_MY_RESULT; 
    public 
    { Public declarations } 
    end; 

var 
    ThreadingForm: TThreadingForm; 

implementation 

{$R *.dfm} 

{ TMyThread } 

constructor TMyThread.Create(CreateSuspended: Boolean); 
begin 
    FKilled := False; 
    InitializeCriticalSection(FListLock); 
    FList := TList.Create; 
    FJobAdded := TEvent.Create(nil, True, False, 'job.added'); 
    inherited; 
end; 

destructor TMyThread.Destroy; 
begin 
    FList.Free; 
    FJobAdded.Free; 
    DeleteCriticalSection(FListLock); 
    inherited; 
end; 

procedure TMyThread.DoJob(AJob: Integer); 
var 
    res: Integer; 
begin 
    res := AJob * AJob * AJob * AJob * AJob * AJob; 
    Sleep(1000); // so it would take some time 
    PostMessage(ThreadingForm.Handle, WM_MY_RESULT, res, 0); 
end; 

procedure TMyThread.Execute; 
begin 
    inherited; 
    while not FKilled or not Self.Terminated do 
    begin 
    EnterCriticalSection(FListLock); 
    if JobCount > 0 then 
    begin 
     LeaveCriticalSection(FListLock); 
     DoJob(GetJob) 
    end 
    else 
    begin 
     FJobAdded.ResetEvent; 
     LeaveCriticalSection(FListLock); 
     FJobAdded.WaitFor(10000); 
    end; 
    end; 
end; 

function TMyThread.GetJob: Integer; 
begin 
    EnterCriticalSection(FListLock); 
    try 
    Result := Integer(FList[0]); 
    FList.Delete(0); 
    finally 
    LeaveCriticalSection(FListLock); 
    end; 
end; 

function TMyThread.JobCount: Integer; 
begin 
    EnterCriticalSection(FListLock); 
    Result := FList.Count; 
    LeaveCriticalSection(FListLock); 
end; 

procedure TMyThread.Kill; 
begin 
    FKilled := True; 
    FJobAdded.SetEvent; 
    Terminate; 
end; 

procedure TMyThread.PushJob(AJob: Integer); 
begin 
    EnterCriticalSection(FListLock); 
    try 
    FList.Add(Pointer(AJob)); 
    FJobAdded.SetEvent; 
    finally 
    LeaveCriticalSection(FListLock); 
    end; 
end; 

{ TThreadingForm } 

procedure TThreadingForm.OnMyResultMessage(var Msg: TMessage); 
begin 
    lstResults.Items.Add(IntToStr(Msg.WParam)); 
end; 

procedure TThreadingForm.FormCreate(Sender: TObject); 
begin 
    FThread := TMyThread.Create(False); 
end; 

procedure TThreadingForm.FormDestroy(Sender: TObject); 
begin 
    FThread.Kill; 
    FThread.WaitFor; 
    FThread.Free; 
end; 

procedure TThreadingForm.btnClick(Sender: TObject); 
begin 
    FThread.PushJob(se.Value); 
end; 
+0

你的線程類編碼不好當它在析構函數中已經被釋放時訪問Execute方法中的一個私有對象字段時會引發一個AV爲了防止你 – mghie 2009-11-15 15:37:43

+0

是的,我同意...我想我真的沒有想到通過正確的一切... – Egon 2009-11-15 19:30:05

+0

恕我直言,要求輸入/離開一個關鍵部分應包裹一個嘗試..最後,以避免離開CS鎖定,如果發生異常。 – 2009-11-16 10:23:48