Question

La méthode de reprise de TThread est obsolète en D2010. Alors, je pensais que cela devrait maintenant fonctionner comme ceci:

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

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

Malheureusement, une exception "Je ne peux pas appeler Start sur un thread en cours ou survenu" ... me semble étrange compte tenu du fait que la documentation indique que je devrais appeler Start sur un thread créé en mode suspendu. <

Qu'est-ce qui me manque ici?

Était-ce utile?

La solution

La raison en est qu'un sujet est pas supposé se lancer lui-même .

Le thread ne sait jamais quand l'initialisation est terminée. La construction n’est pas la même chose que l’initialisation (la construction doit toujours être courte et sans exception; l’initialisation est effectuée après la construction).

Une situation similaire est un TDataSet : aucun constructeur TDataSet ne doit jamais appeler Ouvrir , ni définir Actif: = True .

Voir aussi ceci entrée de blog de Wings of Wind .

Vous devriez soit:

  • Créez le TMyThread suspendu en appelant Create (true) et effectuez le démarrage en dehors de votre classe TMyThread
  • Créez TMyThread non suspendu en vous assurant que le constructeur Create effectue une initialisation complète et laissez TThread.AfterConstruction démarrez le fil de discussion.

Explication de l'utilisation de TThread :

En gros, un fil de discussion ne devrait être que cela: l’encapsulation du contexte sur lequel le code est exécuté.

Le code réel (la logique métier) qui est exécuté doit alors se trouver dans d'autres classes.

En découplant ces deux éléments, vous gagnez beaucoup de flexibilité, en particulier en lançant votre logique métier à partir de plusieurs emplacements (ce qui est très pratique pour écrire des tests unitaires!).

C’est le genre de cadre que vous pourriez utiliser pour cela:

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.

Vous pouvez même en faire un événement basé sur quelque chose comme ceci:

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.

Ou adaptez-le comme suit pour les descendants DUnit TTestCase:

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

Autres conseils

Réponse courte: appelez hérité Create (false) et omettez Start!

Le démarrage réel d'un thread non créé par la création est réalisé dans AfterConstruction, appelée après l'appel de tous les constructeurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top