2012-06-28 12 views
9

Tengo el siguiente código de hebra que se ejecuta correctamente la primera vez. Después de que de vez en cuando me sale un AV en el método Execute de la rosca, por ejemploDelphi: el temporizador dentro de la hebra genera AV

salida de la depuración: Acceso TProcesses.Execute violación en la dirección 00409C8C en el módulo 'ListenOutputDebugString.exe'. Leer de dirección 08070610 Proceso ListenOutputDebugString.exe (740)

no sé lo que está generando este 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

Nunca llame al método 'Execute' explícitamente, es el error más obvio en su código – kludg

Respuesta

29

Nunca utilizaría el temporizador en un hilo. En su lugar, crearía un evento de sistema y lo esperaría en el ciclo de ejecución del subproceso durante un tiempo específico con la función WaitForSingleObject. Esta función espera hasta que el objeto especificado (en este caso, el evento) se encuentre en estado señalizado o transcurra el intervalo de tiempo de espera.

El principio es fácil, creará el evento en el estado no señalizado y lo mantendrá en ese estado hasta que el hilo termine. Esto hará que la función WaitForSingleObject expire el tiempo de espera cada vez que bloquea el ciclo de ejecución de subprocesos durante el tiempo especificado en la llamada a la función. Una vez que decida terminar su hilo, simplemente establezca el indicador de finalización del hilo (en el que debe pedir todo lo que pueda) y establezca ese evento en el estado señalado, lo que hace que la función WaitForSingleObject vuelva inmediatamente.

Aquí se muestra un ejemplo que simula un temporizador hilo (con 2 segundos de intervalo = 2000 ms utilizados como un segundo parámetro en WaitForSingleObject llamadas de función):

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 Este es un bello y limpio ejemplo de cómo hacer lo que se necesita aquí. –

+0

Este código no es correcto, porque la aplicación puede finalizar antes de que el hilo termine su trabajo. –

+0

@TheNorthStar probando 'if not Terminated' before Synchronize sería suficiente para hacer el trabajo? – Darkendorf

1

En primer lugar, en el constructor TProcesses.Create (aGrid: TJvStringGrid) ; usted tiene:

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

Aquí OverrideOnTerminate never fire. Probablemente quieras atrapar hilo OnTerminate.

En segundo lugar, crea un subproceso en estado de ejecución heredado Create (falso); entonces Execute se llama automáticamente. Cuando Execute finaliza, llama a DoTerminate y se destruye el hilo.

A continuación, cuando el temporizador enciende OnTimer, usted llama varias veces Ejecutar; Aquí el hilo ya no existe. El temporizador no se libera e intenta iniciar un hilo muerto.

Usted necesidad de reescribir su código siguiendo algunas reglas:

  1. Ejecutar deben funcionar continuamente. Puede poner hilo para "dormir" usando WaitForSingleObject/WaitForMultipleObjects. Eche un vistazo a la ayuda de MSDN.
  2. Estas funciones tienen el parámetro Timeout, por lo que no necesita TTimer en absoluto.

[EDIT] he encontrado algunos muestra útil para usted (lo siento, no está probado por mí):

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; 

Es necesario ajustar ligeramente este. Agregue un objeto más para esperar: un evento creado con la función CreateEvent. Cuando necesite finalizar el hilo instantáneamente solo llame a la función SetEvent.

+0

correcto. Cambié el evento OverrideOnTerminate to OnTerminate del hilo. – RBA

+1

como dijo David Heffernan, mezclar TTimer y TThread es una práctica realmente mala. – Marcodor

0

Su hilo está trabajando en los controles de GUI (Asumiendo que TJvStringGrid es un control de GUI). Esa nunca es una buena idea y puede dar resultados inesperados. No hay otro hilo, entonces el hilo principal debe tocar las cosas de la GUI.

+0

El uso de 'Sincronizar' en el código de la pregunta coloca todo el código de UI en el hilo principal. –

+0

Estoy usando Sincronizar para sincronizar los controles de la GUI – RBA

+0

Lo siento, tienes razón. Pasé por alto eso. – Birger

1

¿Se puede verificar si el temporizador pertenece realmente al nuevo hilo (TProcess) o al principal? Los temporizadores en Windows son "propiedad" (en términos del administrador de recursos) por hilos, no por procesos. Si su temporizador es propiedad del hilo principal, entonces el evento OnTimer se ejecutará en el contexto del hilo principal, e incluso si llama explícitamente a Execute, la llamada seguirá estando en el contexto del hilo principal, sin importar si Execute es un "procedimiento de objeto" que pasa a ser un descendiente TThread.

Y no puede llamar explícitamente a Execute de todos modos. Este procedimiento se llama (en el contexto del nuevo hilo) cuando se ejecuta el hilo.

Mejor intente esto: Dentro de Ejecutar, cree el temporizador usando las funciones de API de Windows, y espere infinitamente (SleepEx) con el parámetro de alertable establecido en TRUE. Entonces, el temporizador disparará en el contexto del nuevo hilo.Alternativamente, en el evento OnTimer (en el contexto del hilo principal) puede estar enviando llamadas al procedimiento APC al hilo del trabajador (todavía tendrá que esperar en SleepEx y establecer alertable a TRUE). Una alternativa completamente diferente: en el evento OnTimer cree el objeto thread y realice el procesamiento normal dentro de Execute - FreeOnTerminate debe establecerse en true para que el objeto se libere después de finalizar.

Y una nota final, no estoy seguro de si puede pasar esa función EnumProcess (una función declarada dentro de un "procedimiento de objeto" ???) a una llamada de WinApi. Esto bien puede estar causando los bloqueos. Creo que necesitas una función declarada a nivel global.

+0

En cuanto a la nota final, es una mala práctica en mi opinión, pero en el compilador de 32 bits funciona bien siempre que no se refiera a ninguna variable en los ámbitos adjuntos. –

5

TTimer no es seguro para subprocesos. Período. Ni siquiera intentes utilizarlo con un hilo de trabajo.

Está creando instancias del TTimer en el constructor del subproceso de trabajo, lo que significa que se crea una instancia en el contexto del subproceso que está creando el subproceso de trabajo, no el contexto del subproceso de trabajo mismo. Eso también significa que el temporizador se ejecutará en el mismo contexto de subproceso y el evento andler OnTimer no se desencadenará en el contexto de la cadena de trabajo (si es que lo hace), por lo que el cuerpo de su controlador OnTimer debe ser seguro para subprocesos.

Para que se active el evento TTimer.OnTimer en el contexto del subproceso de trabajo, debe crear una instancia del TTimer dentro del método Execute() del subproceso. Pero eso tiene otro conjunto de trampas. TTimer crea una ventana oculta utilizando AllocateHWnd(), que no es segura para subprocesos y no se puede usar de forma segura fuera del contexto del hilo principal. Además, TTimer requiere que el contexto de la cadena de creación tenga un bucle de mensaje activo, que no es el hilo.

Para hacer lo que está intentando, debe cambiar a utilizar la función Win32 API SetTimer() directamente (lo que le permite omitir la necesidad de una ventana) y luego agregar un bucle de mensaje a su hilo (que aún necesita ya sea que use una ventana o no), o cambie a un mecanismo de sincronización diferente. Puede utilizar un temporizador de espera a través de CreateWaitableTimer() y WaitForSingleObject(), en cuyo caso no necesita una ventana o un mensaje loopp. O puede usar un temporizador multimedia a través del timeSetEvent() (solo asegúrese de que la devolución de llamada del temporizador multimedia esté protegida contra subprocesos porque el temporizador se ejecutará en su propio hilo).

+0

+1, sobre el 'SetTimer', no necesita tener un bucle de mensaje. Puede usar una función de devolución de llamada en su lugar. – TLama

+2

@TLama No, aún necesita un bucle de mensaje. Las funciones de devolución de llamada se envían desde su llamada a GetMessage, al igual que los mensajes no en cola. –

+0

@David, tienes razón, retomando; incluso se nota en la referencia. – TLama

Cuestiones relacionadas