The reason is that a Thread is not supposed to start itself.
Wątek nigdy nie wie, kiedy inicjowanie zostało zakończone. Konstrukcja nie jest taka sama jak inicjalizacja (konstrukcja powinna być zawsze krótka i bez wyjątków, dalsza inicjalizacja odbywa się po zakończeniu budowy).
Podobna sytuacja jest TDataSet: brak TDataSet konstruktor powinien zawsze zadzwonić Otwórz lub ustawić Aktywny: = True.
Zobacz także ten blog entry by Wings of Wind.
Powinieneś albo:
- Tworzenie TMyThread zawieszony przez wywołanie Tworzenie (prawda) i wykonać Start poza klasą TMyThread
- Tworzenie TMyThread non-suspeneded, upewniając Tworzenie konstruktor robi pełnej inicjalizacji i niech TThread.AfterConstruction uruchomi wątek.
Wyjaśnienie TThread użytkowania:
Zasadniczo wątek powinien być po prostu, że: hermetyzacja kontekście, na których kod jest wykonywany.
Faktyczny kod (logika biznesowa), który jest wykonywany, powinien znajdować się w innych klasach.
Oddzielając te dwa, zyskujesz dużą elastyczność, szczególnie inicjując logikę biznesową z wielu miejsc (co jest bardzo wygodne przy pisaniu testów jednostkowych!).
Jest to rodzaj ramy można użyć do tego:
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.
Można nawet zrobić to wydarzenie w oparciu o coś takiego:
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.
lub dostosowanie go do DUnit TTestCase potomkom jak to:
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
poprawny sposób to 'MyThread: = TMyThread.Create', a następnie' MyThread.Start', wykonujący wszystkie długie inicjalizacje na początku 'Execute'? W jaki sposób użytkownicy klasy powinni wiedzieć, że wątek musi zostać uruchomiony ręcznie? (Dokumentacja na bok) – jpfollenius
Ponieważ zakłada się, że użytkownicy klasy są w pełni zorientowani w programowaniu wielowątkowym, powinien to być drobny szczegół. Nie pozwól, aby wielowątkowa klasa uciekła ludziom, którzy nie są gotowi na wszystkie niuanse programowania wielowątkowego. –
Właściwie, zobacz moją edycję: są na to dwa sposoby. Ogólnie rzecz biorąc, wątek nie powinien wiedzieć o jego inicjalizacji, ponieważ zależy to od czynników spoza zakresu tego wątku. Wiem, że większość klas wątków dotyczy problemu. Ale powinny być podzielone na dwie: sama klasa wątku, która właśnie wykonuje "kod", i klasa biznesowa, która wie o tym, jaki kod powinien zostać wykonany iw jakiej kolejności (inicjalizacja, główny blok, finalizacja). –