2009-11-14 9 views
9

ho bisogno di creare un thread in Delphi con le seguenti caratteristiche:Delphi thread che attende i dati, elabora, per poi riprendere in attesa

  • attende che il thread principale aggiunge i dati a una coda condivisa.
  • Elabora tutti i dati nella coda, restituendo i risultati al thread principale (per quest'ultima parte semplicemente invierò messaggi alla finestra principale). L'elaborazione richiede molto tempo, quindi è possibile aggiungere nuovi dati alla coda mentre il thread di lavoro elabora le voci precedenti.
  • Riprende in attesa, utilizzando il minor numero possibile di cicli cpu.

Non riesco a inviare messaggi alla discussione, poiché non ha una maniglia di finestra.

Devo usare qualche variante di WaitForObject? Se sì, quale sarebbe l'attesa? In caso contrario, come posso mantenere il thread in attesa, quindi riattivarlo quando i nuovi dati scendono dalla coda?

Ho letto Multithreading - The Delphi Way, che non sembra rispondere alla mia domanda. Forse OmniThreadLibrary può fare quello che mi serve; Non posso dirlo perché c'è poca documentazione. Non ne so abbastanza dei thread in generale per capire se la libreria aiuterà qui e come usarla (o anche perché usarla invece di lavorare solo con i discendenti di TThread).

+2

Lei scrive: "Ho letto Multithreading - La Via Delphi, che non sembra rispondere alla mia domanda." ma lo fa nel capitolo 9. La relazione produttore-consumatore è ciò che stai cercando, ei semafori sono davvero un modo per implementare tali code. – mghie

risposta

13

OmniThreadLibrary può sicuramente aiutarti. Il test 5 della distribuzione OTL dovrebbe aiutarti a iniziare.

In questa demo, il pulsante "Start" crea il thread e imposta alcuni parametri e timer (che è possibile rimuovere nel codice se non necessario). "Cambia messaggio" invia un messaggio al thread e questo messaggio viene elaborato nel metodo OMChangeMessage del thread. Thread quindi invia alcune informazioni al client (OMSendMessage in questa demo, ma puoi farlo nello stesso messaggio in cui farai il tuo lavoro) e il thread principale riceve questo messaggio tramite il componente OmniEventMonitor. Il pulsante "Stop" arresta il thread di lavoro.

Se altri messaggi arrivano mentre il thread è occupato, verranno accodati ed elaborati non appena il metodo di lavoro ha completato il proprio lavoro. Quando non c'è niente da fare, thread attenderà il prossimo messaggio usando zero cicli CPU nel processo.

EDIT

In Delphi 2009 e al di sopra, il modello Background Worker fornisce una soluzione più semplice.

+2

OmniThreadLibrary ha molto potenziale se solo fosse meglio documentato ... – Remko

+0

Sì lo so, lo so ... :( La documentazione è la prima cosa sulla lista dopo la versione 1.04 – gabr

+1

La documentazione sta crescendo lentamente: http://www.leanpub.com/omnithreadlibrary – gabr

1

È possibile inviare messaggi a un thread, anche se non ha una maniglia di finestra. Basta usare PostThreadMessage() anziché SendMessage() o PostMessage(). Ci saranno ulteriori informazioni qui su StackOverflow se cerchi PostThreadMessage() nel tag [delphi] - Non penso che sia una buona idea duplicare tutto qui.

Ma se non si è ben informati sulla programmazione dei thread, iniziare con OTL invece di quelli di basso livello potrebbe essere una buona cosa.

2

WaitForSingleObject() può attendere diversi tipi di oggetti di sincronizzazione. È possibile utilizzare un oggetto di sincronizzazione "evento" di Windows (che non ha nulla a che fare con un evento Delphi). Si crea l'evento (c'è un wrapper Delphi TEvent in SyncObjs, IIRC) e si chiama WaitForSingleObject per attendere che l'evento venga segnalato. Quando si deve riattivare il thread, si chiama SetEvent per inserire l'evento nello stato segnalato e restituisce WaitForSingleObject.Puoi avere un thread per aspettare uno (o tutti) di più oggetti usando WaitForMultipleObjects() - ti dirà anche quale oggetto è stato segnalato.

1

Ecco un semplice esempio di come si può fare ...

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

La tua classe thread è codificata male e genererà un AV quando accede a uno dei campi dell'oggetto privato nel metodo 'Execute' quando sono già stati liberati nel distruttore. ho bisogno di terminare il thread e 'WaitFor' prima di liberare qualsiasi cosa – mghie

+0

Sì, sono d'accordo ... Penso che davvero non ho pensato tutto attraverso correttamente ... – Egon

+0

IMHO chiama per entrare/lasciare una sezione critica dovrebbe essere avvolto da una prova..finalmente per evitare di lasciare il CS bloccato se si verifica un'eccezione –

Problemi correlati