2009-11-14 97 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