multithreading - tof - temporizador tonr
Delphi: el temporizador dentro de la secuencia genera AV (5)
¿Puedes verificar si el temporizador es realmente propiedad del nuevo hilo (TProcess) o del 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: Ejecute dentro, cree el temporizador usando las funciones api de Windows, y espere infinitamente (SleepEx) con el parámetro 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.
Tengo el siguiente código de subproceso que se ejecuta correctamente la primera vez. Después de eso, de vez en cuando obtengo un AV en el método Execute del hilo, por ej.
Salida de depuración: violación de TProcesses.Execute Access en la dirección 00409C8C en el módulo ''ListenOutputDebugString.exe''. Lectura de la dirección 08070610 Proceso ListenOutputDebugString.exe (740)
No sé qué 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.
Nunca usaría el temporizador en un hilo. En su lugar, crearía un evento del 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ñalado y lo mantendrá en ese estado hasta que el hilo termine. Esto hará que la función WaitForSingleObject
tiempo de espera cada vez que bloquea el ciclo de ejecución de la secuencia 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 (sobre el cual debe preguntar tanto como pueda) y establezca ese evento en el estado señalado, lo que hace que la función WaitForSingleObject
regrese inmediatamente.
Aquí hay un ejemplo que simula un temporizador de subprocesos (con un intervalo de 2 segundos = 2000 ms utilizados como un segundo parámetro en las llamadas a la función WaitForSingleObject
):
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.
Primero, en el constructor TProcesses.Create (aGrid: TJvStringGrid); tienes:
FTimer.OnTimer := OverrideOnTerminate;
FTimer.OnTimer := OverrideOnTimer;
Aquí OverrideOnTerminate nunca se dispara. Probablemente quieras atrapar hilo OnTerminate.
En segundo lugar, crea un hilo 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.
Luego, cuando el temporizador enciende OnTimer, usted llama varias veces Execute; Aquí el hilo ya no existe. El temporizador no se libera e intenta iniciar un hilo muerto.
Necesita volver a escribir su código siguiendo algunas reglas:
- Execute debería ejecutarse continuamente. Puede poner hilo para "dormir" usando WaitForSingleObject / WaitForMultipleObjects. Eche un vistazo a la ayuda de MSDN.
- Estas funciones tienen el parámetro Timeout, por lo que no necesita TTimer en absoluto.
[EDIT] Encontré una muestra útil para ti (lo siento, no la he probado):
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;
Necesitas ajustar esto ligeramente. 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.
Su hilo está trabajando en controles GUI (Asumiendo que TJvStringGrid es un control 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.
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 en el contexto del subproceso de trabajo mismo. Eso también significa que el temporizador se ejecutará en ese mismo contexto de subproceso y que el OnTimer
eventos OnTimer no se activará 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 el evento TTimer.OnTimer
se desencadene 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 seguro 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 TTimer
creación tenga un bucle de mensaje activo, que no es el hilo.
Para hacer lo que está intentando, debe cambiar a usar 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 si use una ventana o no), o cambie a un mecanismo de sincronización diferente. Puede usar un temporizador de espera mediante CreateWaitableTimer()
y WaitForSingleObject()
, en cuyo caso no necesita una ventana o un mensaje loopp. O puede usar un temporizador multimedia a través de 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).