2009-10-26 17 views
16

El método de currículum de TThread está en desuso en D2010. Por lo tanto, pensé que ahora debería funcionar así:¿Reanudar hilo suspendido en Delphi 2010?

TMyThread = class (TThread) 
protected 
    Execute; override; 
public 
    constructor Create; 
end; 
... 

TMyThread.Create; 
begin 
    inherited Create (True); 
    ... 
    Start; 
end; 

desgracia consigo una excepción "No se puede llamar a empezar en una carrera o hilo supsended" ... que parece extraño para mí teniendo en cuenta el hecho de que la documentación me dice que debería llamar a Start en un hilo creado en modo suspendido.

¿Qué me falta aquí?

Respuesta

19

The reason is that a Thread is not supposed to start itself.

El subproceso nunca se sabe cuando se completa la inicialización. La construcción no es lo mismo que la inicialización (la construcción siempre debe ser corta y sin excepciones, la posterior inicialización se realiza después de la construcción).

Una situación similar es un TDataSet : no TDataSet constructor siempre debe llamar abierto, o conjunto activo: = True.

Ver también este artículo blog entry by Wings of Wind.

Usted deberá:

  • Crear el TMyThread suspendida llamando Crear (verdadero) y efectuar la puesta fuera de su clase TMyThread
  • Crear la no suspeneded, asegurándose de que el Crear constructor hace inicialización completa TMyThread y deje que TThread.AfterConstruction inicie el hilo.

Explicación del uso TThread:

Básicamente, un hilo debe ser sólo eso: la encapsulación del contexto en el que se ejecuta el código.

El código real (la lógica de negocio) que se ejecuta debe estar en otras clases.

Al desacoplar esos dos, obtiene mucha flexibilidad, especialmente al iniciar su lógica de negocios desde varios lugares (¡lo cual es muy conveniente al escribir pruebas de unidad!).

Este es el tipo de marco que podría utilizar para ello:

unit DecoupledThreadUnit; 

interface 

uses 
    Classes; 

type 
    TDecoupledThread = class(TThread) 
    strict protected 
    //1 called in the context of the thread 
    procedure DoExecute; virtual; 
    //1 Called in the context of the creating thread (before context of the new thread actualy lives) 
    procedure DoSetUp; virtual; 
    //1 called in the context of the thread right after OnTerminate, but before the thread actually dies 
    procedure DoTearDown; virtual; 
    protected 
    procedure DoTerminate; override; 
    procedure Execute; override; 
    public 
    constructor Create; 
    procedure AfterConstruction; override; 
    end; 

implementation 

constructor TDecoupledThread.Create; 
begin 
    // create suspended, so that AfterConstruction can call DoSetup(); 
    inherited Create(True); 
end; 

procedure TDecoupledThread.AfterConstruction; 
begin 
    // DoSetUp() needs to be called without the new thread in suspended state 
    DoSetUp(); 
    // this will unsuspend the underlying thread 
    inherited AfterConstruction; 
end; 

procedure TDecoupledThread.DoExecute; 
begin 
end; 

procedure TDecoupledThread.DoSetUp; 
begin 
end; 

procedure TDecoupledThread.DoTearDown; 
begin 
end; 

procedure TDecoupledThread.DoTerminate; 
begin 
    inherited DoTerminate(); 
    // call DoTearDown on in the thread context right before it dies: 
    DoTearDown(); 
end; 

procedure TDecoupledThread.Execute; 
begin 
    // call DoExecute on in the thread context 
    DoExecute(); 
end; 

end. 

Incluso podría hacerlo evento basado en algo como esto:

unit EventedThreadUnit; 

interface 

uses 
    Classes, 
    DecoupledThreadUnit; 

type 
    TCustomEventedThread = class(TDecoupledThread) 
    private 
    FOnExecute: TNotifyEvent; 
    FOnSetUp: TNotifyEvent; 
    FOnTearDown: TNotifyEvent; 
    strict protected 
    procedure DoExecute; override; 
    procedure DoSetUp; override; 
    procedure DoTearDown; override; 
    public 
    property OnExecute: TNotifyEvent read FOnExecute write FOnExecute; 
    property OnSetUp: TNotifyEvent read FOnSetUp write FOnSetUp; 
    property OnTearDown: TNotifyEvent read FOnTearDown write FOnTearDown; 
    end; 

    // in case you want to use RTTI 
    TEventedThread = class(TCustomEventedThread) 
    published 
    property OnExecute; 
    property OnSetUp; 
    property OnTearDown; 
    end; 

implementation 

{ TCustomEventedThread } 

procedure TCustomEventedThread.DoExecute; 
var 
    TheOnExecute: TNotifyEvent; 
begin 
    inherited; 
    TheOnExecute := OnExecute; 
    if Assigned(TheOnExecute) then 
    TheOnExecute(Self); 
end; 

procedure TCustomEventedThread.DoSetUp; 
var 
    TheOnSetUp: TNotifyEvent; 
begin 
    inherited; 
    TheOnSetUp := OnSetUp; 
    if Assigned(TheOnSetUp) then 
    TheOnSetUp(Self); 
end; 

procedure TCustomEventedThread.DoTearDown; 
var 
    TheOnTearDown: TNotifyEvent; 
begin 
    inherited; 
    TheOnTearDown := OnTearDown; 
    if Assigned(TheOnTearDown) then 
    TheOnTearDown(Self); 
end; 

end. 

o adaptarlo para los descendientes DUnit TTestCase como esto:

unit TestCaseThreadUnit; 

interface 

uses 
    DecoupledThreadUnit, 
    TestFramework; 

type 
    TTestCaseRanEvent = procedure (Sender: TObject; const TestResult: TTestResult) of object; 
    TTestCaseThread = class(TDecoupledThread) 
    strict private 
    FTestCase: TTestCase; 
    strict protected 
    procedure DoTestCaseRan(const TestResult: TTestResult); virtual; 
    function GetTestCase: TTestCase; virtual; 
    procedure SetTestCase(const Value: TTestCase); virtual; 
    protected 
    procedure DoExecute; override; 
    procedure DoSetUp; override; 
    procedure DoTearDown; override; 
    public 
    constructor Create(const TestCase: TTestCase); 
    property TestCase: TTestCase read GetTestCase write SetTestCase; 
    end; 

implementation 

constructor TTestCaseThread.Create(const TestCase: TTestCase); 
begin 
    inherited Create(); 
    Self.TestCase := TestCase; 
end; 

procedure TTestCaseThread.DoExecute; 
var 
    TestResult: TTestResult; 
begin 
    if Assigned(TestCase) then 
    begin 
    // this will call SetUp and TearDown on the TestCase 
    TestResult := TestCase.Run(); 
    try 
     DoTestCaseRan(TestResult); 
    finally 
     TestResult.Free; 
    end; 
    end 
    else 
    inherited DoExecute(); 
end; 

procedure TTestCaseThread.DoTestCaseRan(const TestResult: TTestResult); 
begin 
end; 

function TTestCaseThread.GetTestCase: TTestCase; 
begin 
    Result := FTestCase; 
end; 

procedure TTestCaseThread.SetTestCase(const Value: TTestCase); 
begin 
    FTestCase := Value; 
end; 

procedure TTestCaseThread.DoSetUp; 
begin 
    if not Assigned(TestCase) then 
    inherited DoSetUp(); 
end; 

procedure TTestCaseThread.DoTearDown; 
begin 
    if not Assigned(TestCase) then 
    inherited DoTearDown(); 
end; 

end. 

--jeroen

+0

la forma correcta sería 'MyThread: = TMyThread.Create' y luego' MyThread.Start', realizando todas las inicializaciones largas al comienzo de 'Execute'? ¿Cómo se supone que los usuarios de la clase saben que el hilo debe iniciarse manualmente? (Documentación aparte) – jpfollenius

+0

Como se supone que los usuarios de la clase están completamente versados ​​en la programación multiproceso, eso debería ser un detalle menor. No permita que una clase de subprocesos múltiples se escape a personas que no están preparadas para manejar todos los matices de la programación multiproceso. –

+0

En realidad, ver mi edición: hay dos formas de hacerlo. En general, un hilo no debería saber sobre su inicialización, ya que eso depende de factores fuera del alcance de ese hilo. Sé que la mayoría de las clases de subprocesos son específicas de un problema. Pero deberían dividirse en dos: la clase de subprocesos en sí que solo ejecuta 'código', y una clase de negocios que sabe qué código debe ejecutarse y en qué orden (inicialización, bloque principal, finalización). –

14

Respuesta corta: llamada heredada Create (false) and omitt the Start!

El inicio real de un subproceso que no se ha creado suspendido se realiza en AfterConstruction, que se llama después de haber llamado a todos los constructores.

+2

Tenga en cuenta que este es un desarrollo relativamente reciente. Las versiones antiguas realmente comenzarían a ejecutarse tan pronto como el constructor heredado terminara de ejecutarse. Sin embargo, hay una solución muy fácil a eso: llamar al constructor heredado ** último **. (Hay pocas razones para llamarlo primero, un descendiente rara vez necesita alguno de los valores de propiedad que establece el constructor TThread base). –

+2

Mencionó D2010 explícitamente ... –

+0

No, @ Jan, el constructor heredado no tiene la libertad de establecer su campos a cero. No puede acceder a sus campos porque fue escrito y compilado antes de que esos campos existieran, por lo que no puede hacer referencia a ellos. (Si está pensando en el constructor que utiliza ZeroMemory para borrarse, eso sería desastroso en * any * class, no solo TThread. Todos los campos se ponen a cero antes de * cualquier * constructor se ejecuta. Consulte TObject.InitInstance). –

Cuestiones relacionadas