2009-11-14 24 views
9

Necesito crear un hilo en Delphi con las siguientes características:Delphi hilo que espera datos, los procesa y luego vuelve a la espera

  • espera hasta que el hilo principal añade datos a una cola compartida.
  • Procesa todos los datos en la cola, devolviendo los resultados al hilo principal (para esta última parte solo enviaré mensajes a la ventana principal). El procesamiento lleva mucho tiempo, por lo que se pueden agregar nuevos datos a la cola mientras el hilo de trabajo está procesando las entradas anteriores.
  • Reanuda la espera, utilizando la menor cantidad posible de ciclos de CPU.

No puedo enviar mensajes a la secuencia, ya que no tiene un identificador de ventana.

¿Debo utilizar alguna variante de WaitForObject? Si es así, ¿cuál sería la espera? De lo contrario, ¿cómo puedo mantener el hilo en espera y luego despertarlo cuando aparezcan nuevos datos en la cola?

He leído Multithreading - The Delphi Way, que no parece responder a mi pregunta. Tal vez OmniThreadLibrary puede hacer lo que necesito; No puedo decir que hay poca documentación. No sé lo suficiente sobre los hilos en general para averiguar si la biblioteca ayudará aquí y cómo usarla (o incluso por qué usarla en lugar de simplemente trabajar con descendientes TThread).

+2

Usted escribe: "He leído multihilo - El Camino de Delphi, que no parece responder a mi pregunta." pero lo hace en el capítulo 9. La relación productor-consumidor es lo que está buscando, y los semáforos son de hecho una forma de implementar tales colas. – mghie

Respuesta

13

OmniThreadLibrary definitivamente puede ayudarle aquí. La prueba 5 de la distribución OTL debería ayudarlo a comenzar.

En esta demostración, el botón "Inicio" crea la secuencia y establece algunos parámetros y el temporizador (que puede eliminar en su código si no es necesario). "Cambiar mensaje" envía un mensaje al hilo y este mensaje se procesa en el método OMChangeMessage del hilo. El subproceso envía luego cierta información al cliente (OMSendMessage en esta demostración, pero puede hacerlo en el mismo mensaje en el que hará su trabajo) y el hilo principal recibe este mensaje a través del componente OmniEventMonitor. El botón "Parar" detiene el hilo de trabajo.

Si llegan más mensajes mientras el hilo está ocupado, se pondrán en cola y se procesarán tan pronto como su método de trabajo haya finalizado su trabajo. Cuando no hay nada que hacer, el hilo esperará al siguiente mensaje utilizando cero ciclos de CPU en el proceso.

EDITAR

En Delphi 2009 y anteriormente, el patrón Background Worker proporciona una solución más simple.

+2

OmniThreadLibrary tiene mucho potencial si solo estuviera mejor documentado ... – Remko

+0

Sí lo sé, lo sé ... :( La documentación es lo primero en la lista después de la versión 1.04. – gabr

+1

La documentación está creciendo lentamente: http://www.leanpub.com/omnithreadlibrary – gabr

1

Definitivamente puede enviar mensajes a un hilo, aunque no tenga un identificador de ventana. Solo use PostThreadMessage() en lugar de SendMessage() o PostMessage(). Habrá más información aquí en StackOverflow si busca PostThreadMessage() en la etiqueta [delphi]. No creo que sea una buena idea duplicar todo aquí.

Pero si no conoce la programación de subprocesos, entonces comenzar con OTL en lugar de las cosas de bajo nivel puede ser algo bueno.

2

WaitForSingleObject() puede esperar en varios tipos de objetos de sincronización. Puede usar un objeto de sincronización de "eventos" de Windows (que no tiene nada que ver con un evento Delphi). Usted crea el evento (hay un contenedor Delphi TEvent en SyncObjs, IIRC) y llama a WaitForSingleObject para que espere que ese evento sea señalizado. Cuando tenga que activar el hilo, llame a SetEvent para poner el evento en estado señalado y WaitForSingleObject regresa.Puede esperar a que un subproceso espere uno (o todos) de varios objetos utilizando WaitForMultipleObjects(); también le indicará qué objeto se marcó.

1

Aquí está un ejemplo sencillo de cómo puede hacerlo ...

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

Su clase de subprocesos está codificada incorrectamente. Levantará un AV cuando acceda a uno de los campos de objetos privados en el método 'Execute' cuando ya se hayan liberado en el destructor. Necesito terminar el hilo y 'WaitFor' antes de liberar algo. – mghie

+0

Sí, estoy de acuerdo ... Creo que realmente no pensé todo bien ... – Egon

+0

Las llamadas en mi humilde opinión para entrar/salir de una sección crítica deben ser envueltas por un intento ... por último para evitar dejar el CS bloqueado si ocurre una excepción. –

Cuestiones relacionadas