2013-07-19 24 views
9

Ho cercato di rintracciare una perdita di memoria di JvHidControllerClass.pas Jedi VCL, che mi sono imbattuto in questo cambiamento nella storia fonte:Delphi: un thread dovrebbe mai essere creato "non sospeso"?

più vecchio di revisione:

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

revisione corrente:

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

Per esperienza, ho scoperto che se si crea un filo non sospesi:

inherited Create(False); 

poi il filo immediatamente inizia a funzionare. In questo caso si tenterà di accedere a un oggetto che non è stato ancora inizializzato:

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 

subito cercando di riempire Report, e accedere all'oggetto Device. Il problema è che non sono ancora stati inizializzati; quelli sono i prossimi righe dopo il thread è iniziato:

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

Mi rendo conto che è una condizione di competizione; e le probabilità che un utente subisca un arresto durante la produzione sono piuttosto basse, quindi è probabilmente innocuo abbandonare la corsa.

Ma sono via? Mi sto perdendo qualcosa? Chiama:

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

non avviare un thread off e in esecuzione subito? Si tratta davvero di una regressione della condizione di competizione che è stata (intenzionalmente) aggiunta a JVCL? C'è qualche segreto su

CreateSuspended(False); 

che il corretto codice dirende più di:

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

?

Dopo essere stato bruciato chiamando erroneamente

TMyThread.Create(False) 

ho depositato nel mio cervello come mai corretta. C'è un uso valido per far partire subito un thread (quando devi inizializzare i valori)?

+0

Wow !!! JVCL su D5! anche se è stato spento dopo che l'ho lasciato e ho smesso di mantenere la compatibilità con D5. Un sentimento così nostalgico ... –

+1

@ Arioch'The Non essere troppo nostalgico; è JVCL 3.x del 2009. E in senso stretto è la classe HidController originale di Richard Marquand del 2005; con cui ho aiutato un po ' La versione adottata da JVCL subì un enorme * "jcl-ifying" *; ma non ci sono vere differenze; ma tecnicamente sto usando la versione di Richard; così posso correggere l'arresto * use-after-free * che FastMM cattura. –

risposta

12

Questo è un difetto di progettazione di base con l'implementazione Delphi 5 di TThread. Il thread di Windows sottostante viene avviato nel costruttore di TThread. Che porta alla gara che descrivi.

Nella versione Delphi 6 di RTL, il meccanismo di avvio del filo è stato modificato. Da Delphi 6 in poi, il thread viene avviato in TThread.AfterConstruction. E che funziona dopo che il costruttore ha completato. Ciò renderebbe il tuo codice libero da corsa.

In Delphi 6 e versioni successive, il sottoprocesso Windows sottostante viene creato nel costruttore TThread, ma viene creato sospeso utilizzando il flag CREATE_SUSPENDED. Quindi in AfterConstruction, fino a quando TThread.FCreateSuspended è False, il thread viene ripreso.

Un modo per aggirare il problema in Delphi 5 è chiamare il costruttore ereditato per ultimo. Così:

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

Piuttosto brutto, lo so.

Quindi, il tuo approccio di creare il thread sospeso e riprendere una volta completato il costruttore è probabilmente migliore. Questo approccio rispecchia il modo in cui l'RTL risolve il problema in Delphi 6 e versioni successive.

+0

Questo lo spiega. +1 per la lezione di storia! –

+0

Il modo in cui lo faccio sempre è instantiate/destroy direttamente nell'esecuzione del thread. Questo deve essere fatto comunque se hai bisogno di lavorare con cose come COM (come ADO). Quindi, in realtà, ogni volta che scrivo un thread, non realizzo mai alcuna creazione o distruzione o qualcosa del genere in create/destroy. (+1) –

+0

@Jerry va bene fino a quando non è necessario comunicare tra creatore e thread –