2012-06-28 15 views
9

Ho il seguente codice thread che viene eseguito correttamente la prima volta. Dopo di che di tanto in tanto ricevo un AV sul metodo Execute del filo, per esempioDelphi - il timer all'interno del thread genera AV

Debug Output: violazione TProcesses.Execute accesso all'indirizzo 00409C8C nel modulo 'ListenOutputDebugString.exe'. Leggi di indirizzo 08070610 Processo ListenOutputDebugString.exe (740)

non so che cosa sta generando questa AV ...

unit Unit3; 

interface 

uses 
    Classes, 
    StdCtrls, 
    Windows, 
    ExtCtrls, 
    SysUtils, 
    Variants, 
    JvExGrids, 
    JvStringGrid; 

type 
    TProcesses = class(TThread) 
    private 
    { Private declarations } 
    FTimer : TTimer; 
    FGrid : TJvStringGrid; 
    FJobFinished : Boolean; 
    procedure OverrideOnTerminate(Sender: TObject); 
    procedure DoShowData; 
    procedure DoShowErrors; 
    procedure OverrideOnTimer(Sender: TObject); 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(aGrid : TJvStringGrid);overload; 
    end; 

implementation 

{TProcesses } 

var SharedMessage : String; 
    ErrsMess  : String; 
    lp   : Integer; 

constructor TProcesses.Create(aGrid : TJvStringGrid); 
begin 
FreeOnTerminate := True; 
FTimer := TTimer.Create(nil); 
FTimer.OnTimer := OverrideOnTerminate; 
FTimer.OnTimer := OverrideOnTimer; 
FTimer.Interval := 10000; 
FGrid := aGrid; 
inherited Create(false); 
FTimer.Enabled := true; 
FJobFinished := true; 
end; 

procedure TProcesses.DoShowData; 
var wStrList : TStringList; 
    wi,wj : Integer; 
begin 
// FMemo.Lines.Clear; 
for wi := 1 to FGrid.RowCount-1 do 
    for wj := 0 to FGrid.ColCount-1 do 
    FGrid.Cells[wj,wi] := ''; 
try 
    try 
    wStrList := TStringList.Create; 
    wStrList.Delimiter := ';'; 
    wStrList.StrictDelimiter := true; 
    wStrList.DelimitedText := SharedMessage; 
// outputdebugstring(PChar('Processes list '+SharedMessage)); 
    FGrid.RowCount := wStrList.Count div 4; 
    for wi := 0 to wStrList.Count-1 do 
    FGrid.Cells[(wi mod 4), (wi div 4)+1] := wStrList[wi]; 
    Except on e:Exception do 
    OutputDebugString(Pchar('TProcesses.DoShowData '+e.Message)); 
    end; 
finally 
    FreeAndNil(wStrList); 
end; 
end; 

procedure TProcesses.DoShowErrors; 
begin 
// FMemo.Lines.Add('Error '+ ErrsMess); 
FGrid.Cells[1,1] := 'Error '+ ErrsMess; 
ErrsMess := ''; 
end; 

procedure TProcesses.Execute; 
    function EnumProcess(hHwnd: HWND; lParam : integer): boolean; stdcall; 
    var 
    pPid : DWORD; 
    title, ClassName : string; 
    begin 
    //if the returned value in null the 
    //callback has failed, so set to false and exit. 
    if (hHwnd=NULL) then 
    begin 
     result := false; 
    end 
    else 
    begin 
     //additional functions to get more 
     //information about a process. 
     //get the Process Identification number. 
     GetWindowThreadProcessId(hHwnd,pPid); 
     //set a memory area to receive 
     //the process class name 
     SetLength(ClassName, 255); 
     //get the class name and reset the 
     //memory area to the size of the name 
     SetLength(ClassName, 
       GetClassName(hHwnd, 
          PChar(className), 
          Length(className))); 
     SetLength(title, 255); 
     //get the process title; usually displayed 
     //on the top bar in visible process 
     SetLength(title, GetWindowText(hHwnd, PChar(title), Length(title))); 
     //Display the process information 
     //by adding it to a list box 
     SharedMessage := SharedMessage + 
     (className +' ;'+//'Class Name = ' + 
     title +' ;'+//'; Title = ' + 
     IntToStr(hHwnd) +' ;'+ //'; HWND = ' + 
     IntToStr(pPid))+' ;'//'; Pid = ' + 
     ;//   +#13#10; 
     Result := true; 
    end; 
    end; 
begin 
if FJobFinished then 
begin 
    try 
    FJobFinished := false; 
    //define the tag flag 
    lp := 0; //globally declared integer 
    //call the windows function with the address 
    //of handling function and show an error message if it fails 
    SharedMessage := ''; 
    if EnumWindows(@EnumProcess,lp) = false then 
    begin 
     ErrsMess := SysErrorMessage(GetLastError); 
     Synchronize(DoShowErrors); 
    end 
    else 
    Synchronize(DoShowData); 
    FJobFinished := true; 
    Except on e:Exception do 
    OutputDebugString(Pchar('TProcesses.Execute '+e.Message)); 
    end; 
end 
end; 

procedure TProcesses.OverrideOnTerminate(Sender: TObject); 
begin 
FTimer.Enabled := false; 
FreeAndNil(FTimer); 
end; 

procedure TProcesses.OverrideOnTimer(Sender: TObject); 
begin 
    Self.Execute; 
end; 

end. 
+3

Non chiamare mai il metodo 'Execute' in modo esplicito, è il bug più ovvio nel codice – kludg

risposta

29

Non userei mai il timer in una discussione. Invece creo un evento di sistema e lo aspetto nel ciclo di esecuzione del thread per un tempo specificato con la funzione WaitForSingleObject. Questa funzione attende fino a quando l'oggetto specificato (in questo caso l'evento) si trova nello stato segnalato o l'intervallo di time-out scade.

Il principio è semplice, creerai l'evento nello stato non segnalato e lo manterrai in quello stato fino a quando il thread non verrà terminato. Ciò determinerà il timeout della funzione WaitForSingleObject ogni volta che blocca il ciclo di esecuzione del thread per il tempo specificato nella chiamata della funzione. Una volta deciso di terminare il thread, basta impostare il flag di terminazione del thread (sul quale si dovrebbe chiedere il più possibile) e impostare tale evento sullo stato segnalato che cosa causa la funzione WaitForSingleObject da restituire immediatamente.

Ecco un esempio che simula un temporizzatore filo (con 2 secondi di intervallo = 2000 ms utilizzato come secondo parametro nella WaitForSingleObject chiamate di funzione):

unit Unit1; 

interface 

uses 
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
    Dialogs; 

type 
    TTimerThread = class(TThread) 
    private 
    FTickEvent: THandle; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(CreateSuspended: Boolean); 
    destructor Destroy; override; 
    procedure FinishThreadExecution; 
    end; 

type 
    TForm1 = class(TForm) 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    private 
    FTimerThread: TTimerThread; 
    public 
    { Public declarations } 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
    ReportMemoryLeaksOnShutdown := True; 
    FTimerThread := TTimerThread.Create(False); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
    FTimerThread.FinishThreadExecution; 
end; 

{ TTimerThread } 

constructor TTimerThread.Create(CreateSuspended: Boolean); 
begin 
    inherited; 
    FreeOnTerminate := True; 
    FTickEvent := CreateEvent(nil, True, False, nil); 
end; 

destructor TTimerThread.Destroy; 
begin 
    CloseHandle(FTickEvent); 
    inherited; 
end; 

procedure TTimerThread.FinishThreadExecution; 
begin 
    Terminate; 
    SetEvent(FTickEvent); 
end; 

procedure TTimerThread.Execute; 
begin 
    while not Terminated do 
    begin 
    if WaitForSingleObject(FTickEvent, 2000) = WAIT_TIMEOUT then 
    begin 
     Synchronize(procedure 
     begin 
      Form1.Tag := Form1.Tag + 1; 
      Form1.Caption := IntToStr(Form1.Tag); 
     end 
    ); 
    end; 
    end; 
end; 

end. 
+8

+1 Questo è un bellissimo esempio di come fare ciò che è necessario qui. –

+0

Questo codice non è corretto, perché l'applicazione può essere terminata prima che il thread finisca il suo lavoro. –

+0

@TheNorthStar testing 'if not Terminated' prima che Synchronize sarebbe sufficiente per fare il lavoro? – Darkendorf

1

In primo luogo, nel costruttore TProcesses.Create (Agrid: TJvStringGrid) ; hai:

FTimer.OnTimer := OverrideOnTerminate; 
FTimer.OnTimer := OverrideOnTimer; 

Qui OverrideOnTerminate non si attiva mai. Probabilmente vuoi catturare il thread OnTerminate.

In secondo luogo, si crea il thread in stato di esecuzione ereditato Crea (falso); così Execute viene chiamato automaticamente. Al termine di Execute, chiama DoTerminate e il thread viene distrutto.

Successivamente, quando il timer attiva OnTimer si chiama più volte Esegui; Qui Thread già potrebbe non esistere. Il timer non viene liberato e si tenta di avviare un thread morto.

È necessario riscrivere il codice seguendo alcune regole:

  1. Execute dovrebbe funzionare in continuo. Puoi mettere il thread in "sleep" usando WaitForSingleObject/WaitForMultipleObjects. Dai un'occhiata alla guida di MSDN.
  2. Queste funzioni hanno il parametro Timeout, quindi non è necessario TTimer.

[EDIT] ho trovato alcuni campioni utili per voi (mi dispiace, non è testato da me):

procedure TProcesses.Execute; 
const 
_SECOND = 10000000; 
var 
lBusy : LongInt; 
hTimer : LongInt; 
liWaitTime : LARGE_INTEGER; 
begin 
    hTimer := CreateWaitableTimer(nil, True, 'WaitableTimer'); 
    liWaitTime.QuadPart := _SECOND * YOUR_NumberOfSeconds; 
    SetWaitableTimer(hTimer, TLargeInteger(liWaitTime), 0, nil, nil, False); 
    repeat 
    lBusy := MsgWaitForMultipleObjects(1, hTimer, False, INFINITE, QS_ALLINPUT); 
    // CODE EXECUTED HERE EVERY YOUR_NumberOfSeconds 
    Until lBusy = WAIT_OBJECT_0; 
    CloseHandle(hTimer); 
end; 

È necessario regolare un po 'questo. Aggiungi un altro oggetto da attendere: un evento creato con la funzione CreateEvent. Quando hai bisogno di terminare istantaneamente thread basta chiamare la funzione SetEvent.

+0

corretto. Ho cambiato l'OverrideOnTerminate in OnTerminate l'evento del thread. – RBA

+1

come ha detto David Heffernan, miscelare TTimer e TThread è una pratica davvero negativa. – Marcodor

0

Il thread sta funzionando sui controlli della GUI (supponendo che TJvStringGrid sia un controllo della GUI). Non è mai una buona idea e può dare risultati inaspettati. Nessun altro thread quindi il thread principale dovrebbe toccare la GUI.

+0

L'uso di 'Sincronizza' nel codice nella domanda mette tutto il codice UI nel thread principale. –

+0

Sto usando Synchronize per sincronizzare i controlli della GUI – RBA

+0

Ci dispiace, hai ragione. L'ho trascurato. – Birger

1

È possibile verificare se il timer è effettivamente di proprietà del nuovo thread (TProcess) o di quello principale? I timer di Windows sono "di proprietà" (in termini di gestore risorse) per thread, non per processi. Se il tuo timer è di proprietà del thread principale, l'evento OnTimer verrà eseguito nel contesto del thread principale e, anche se chiami esplicitamente Execute, la chiamata sarà comunque nel contesto del thread principale, indipendentemente dall'esecuzione di Execute. una "procedura di oggetto" che sembra essere un discendente di TThread.

E non è possibile chiamare esplicitamente Esegui comunque. Questa procedura viene chiamata (nel contesto del nuovo thread) quando viene eseguito il thread.

Meglio provare questo: Inside Execute, crea il timer usando le funzioni di windows api, e attendi infinitamente (SleepEx) con il parametro alertable impostato su TRUE. Quindi il timer scatterà effettivamente nel contesto del nuovo thread.In alternativa nell'evento OnTimer (nel contesto del thread principale) è possibile inviare chiamate di procedure APC al thread di lavoro (sarà comunque necessario attendere in SleepEx e impostare attivabile su TRUE). Un'alternativa completamente diversa: nell'evento OnTimer creare l'oggetto thread e eseguire la normale elaborazione all'interno di Execute - FreeOnTerminate deve essere impostato su true in modo che l'oggetto venga liberato dopo aver terminato.

E una nota finale, non sono sicuro se è possibile passare la funzione EnumProcess (una funzione dichiarata all'interno di una "procedura dell'oggetto" ???) a una chiamata WinApi. Questo potrebbe causare gli arresti anomali. Penso che tu abbia bisogno di una funzione dichiarata a livello globale.

+0

Per quanto riguarda la nota finale, è una cattiva pratica a mio avviso, ma nel compilatore a 32 bit funziona bene fino a quando non si fa riferimento a nessuna variabile negli ambiti di chiusura. –

5

TTimer non è thread-safe. Periodo. Non provare nemmeno a usarlo con un thread di lavoro.

Si sta creando un'istanza di TTimer nel costruttore del thread di lavoro, il che significa che viene creata un'istanza nel contesto del thread che sta creando il thread di lavoro, non il contesto del thread di lavoro stesso. Ciò significa anche che il timer verrà eseguito nello stesso contesto thread e l'evento OnTimer andler non verrà attivato nel contesto del thread di lavoro (se non del tutto), quindi il corpo del tuo gestore OnTimer deve essere thread-safe.

Per attivare l'evento TTimer.OnTimer nel contesto del thread di lavoro, è necessario creare un'istanza dello TTimer all'interno del metodo Execute() del thread. Ma questo ha un'altra serie di insidie. TTimer crea una finestra nascosta utilizzando AllocateHWnd(), che non è thread-safe e non può essere utilizzato in modo sicuro al di fuori del contesto del thread principale. Inoltre, TTimer richiede che il contesto del thread di creazione abbia un ciclo di messaggi attivo, che non è il thread.

Per fare ciò che si sta tentando, è necessario passare all'utilizzo dell'API Win32 SetTimer() direttamente (che consente di ignorare la necessità di una finestra) e quindi aggiungere un ciclo di messaggi al thread (che è ancora necessario se usi una finestra o no), o passa a un diverso meccanismo di temporizzazione. È possibile utilizzare un timer in attesa tramite CreateWaitableTimer() e WaitForSingleObject(), in tal caso non è necessario aprire una finestra o un messaggio. Oppure puoi utilizzare un timer multimediale tramite timeSetEvent() (assicurati solo che il callback del timer multimediale sia sicuro per i thread perché il timer verrà eseguito nella sua stessa thread).

+0

+1, riguardo a "SetTimer", non è necessario avere un loop di messaggi. È possibile utilizzare invece una funzione di callback. – TLama

+2

@TLama No, è ancora necessario un ciclo di messaggi. Le funzioni di callback vengono inviate dalla chiamata a GetMessage, proprio come i messaggi non in coda. –

+0

@David, hai ragione, riprendendo; è persino notato nel riferimento. – TLama

Problemi correlati