2013-07-19 9 views
9

Ich habe ein Speicherleck aufzuspüren versucht, in Jedi VCL JvHidControllerClass.pas, die ich in der Quelle der Geschichte über diese Veränderung kam:Delphi: Sollte jemals ein Thread "nicht suspendiert" erstellt werden?

Ältere Version:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice); 
begin 
    inherited Create(True); 
    Device := Dev; 
    NumBytesRead := 0; 
    SetLength(Report, Dev.Caps.InputReportByteLength); 
end; 

Aktuelle Version:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice); 
begin 
    inherited Create(False); 
    Device := Dev; 
    NumBytesRead := 0; 
    SetLength(Report, Dev.Caps.InputReportByteLength); 
end; 

Aus Erfahrung habe ich entdeckt, dass, wenn Sie einen Thread erstellen nicht suspendiert:

inherited Create(False); 

dann sofort der Faden zu laufen beginnt. In diesem Fall wird versucht, auf ein Objekt zuzugreifen, die noch nicht initialisiert wird:

procedure TJvHidDeviceReadThread.Execute; 
begin 
    while not Terminated do 
    begin 
    FillChar(Report[0], Device.Caps.InputReportByteLength, #0); 
    if Device.ReadFileEx(Report[0], Device.Caps.InputReportByteLength, @DummyReadCompletion) then 

sofort Report zu füllen versuchen, und rufen Sie das Objekt Device. Problem ist, dass sie noch nicht initialisiert wurden; das sind die nächsten Linien, nachdem der Thread gestartet hat:

Device := Dev; 
    NumBytesRead := 0; 
    SetLength(Report, Dev.Caps.InputReportByteLength); 

Ich weiß, dies ist eine Race-Bedingung; und die Wahrscheinlichkeit, dass ein Benutzer einen Crash in der Produktion erlebt, ist ziemlich niedrig, so dass es wahrscheinlich harmlos ist, den Renncrash zu verlassen.

Aber bin ich weg? Fehle ich etwas? Ruft:

BeginThread(nil, 0, @ThreadProc, Pointer(Self), Flags, FThreadID); 

nicht starten einen Thread aus und läuft sofort? Ist das wirklich eine Race-Condition-Regression, die (absichtlich) zu JVCL hinzugefügt wurde? Gibt es ein Geheimnis über

CreateSuspended(False); 

, dass es die richtige Code über macht:

CreateSuspended(True); 
... 
FDataThread.Resume; 

?

Nach versehentlich verbrannt wurden durch

Aufruf
TMyThread.Create(False) 

ich es in meinem Gehirn als nie korrekt eingereicht haben. Gibt es eine gültige Verwendung, um einen Thread sofort starten zu lassen (wenn Sie Werte initialisieren müssen)?

+0

Wow !!! JVCL auf D5! Ich dachte, es wäre verstopft, nachdem ich es beendet hatte und hörte auf, D5-Kompatibilität aufrechtzuerhalten. So ein nostalgisches Gefühl ... –

+1

@ Arioch'Das nicht zu nostalgisch sein; es ist JVCL 3.x von 2009. Und streng genommen ist es Richard Marquands ursprüngliche HidController Klasse von 2005; womit ich ein bisschen geholfen habe. Die Version, die von JVCL übernommen wurde, durchlief ein riesiges * jcl-ifying *; aber es gibt keine wirklichen Unterschiede; aber technisch verwende ich Richards Version; so kann ich den * Gebrauch-nach-frei * -Abfall beheben, den FastMM fängt. –

Antwort

12

Dies ist ein grundlegender Konstruktionsfehler bei der Delphi 5-Implementierung von TThread. Der zugrunde liegende Windows-Thread wird im Konstruktor TThread gestartet. Was zu dem Rennen führt, das du beschreibst.

In der Delphi 6-Version von RTL wurde der Thread-Start-Mechanismus geändert. Ab Delphi 6 wird der Thread in TThread.AfterConstruction gestartet. Und das wird ausgeführt, nachdem der Konstruktor abgeschlossen ist. Das würde deinen Code frei machen.

In Delphi 6 und später wird der zugrunde liegende Windows-Thread im TThread-Konstruktor erstellt, wird jedoch unter Verwendung des Flags CREATE_SUSPENDED ausgesetzt. Dann in AfterConstruction, solange TThread.FCreateSuspendedFalse ist, wird der Thread fortgesetzt.

Eine Möglichkeit, das Problem in Delphi 5 zu umgehen, besteht darin, den geerbten Konstruktor zuletzt aufzurufen. Gefällt mir:

constructor TJvHidDeviceReadThread.CtlCreate(const Dev: TJvHidDevice); 
begin 
    Device := Dev; 
    NumBytesRead := 0; 
    SetLength(Report, Dev.Caps.InputReportByteLength); 
    inherited Create(False); 
end; 

Eher hässlich ich weiß.

Also, Ihr Ansatz des Erstellens des Threads suspendiert und wieder aufzunehmen, sobald der Konstruktor abgeschlossen ist, ist wahrscheinlich besser. Dieser Ansatz spiegelt wider, wie die RTL das Problem in Delphi 6 und höher löst.

+0

Das erklärt es. +1 für den Geschichtsunterricht! –

+0

Die Art, wie ich es immer mache, wird direkt in der Thread-Ausführung instantiiert/zerstört. Das muss sowieso gemacht werden, wenn Sie mit Dingen wie COM (wie ADO) arbeiten müssen. Also, in Wirklichkeit, wenn ich einen Thread schreibe, implementiere ich nie irgendeine Schöpfung oder Zerstörung oder irgendetwas in dieser Hinsicht in create/destroy. (+1) –

+0

@Jerry das ist in Ordnung, bis Sie zwischen Ersteller und Thread kommunizieren müssen –