2010-09-02 9 views
14

Tengo un dilema sobre cómo funcionan los subprocesos en delphi, y por qué en un momento en que un subproceso debe generar una excepción, la excepción no se muestra. abajo es el código con comentarios, tal vez alguien cand explicarme cómo ese hilo, o Delphi, es violaciónes de acceso gestiónMecanismo de excepción de subproceso Delphi

// código hilo

unit Unit2; 

interface 

uses 
    Classes, 
    Dialogs, 
    SysUtils, 
    StdCtrls; 

type 
    TTest = class(TThread) 
    private 
    protected 
    j: Integer; 
    procedure Execute; override; 
    procedure setNr; 
    public 
    aBtn: tbutton; 
    end; 

implementation 


{ TTest } 

procedure TTest.Execute; 
var 
    i     : Integer; 
    a     : TStringList; 
begin 
// make severals operations only for having something to do 
    j := 0; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 
    for i := 0 to 100000000 do 
    j := j + 1; 

    Synchronize(setnr); 
    a[2] := 'dbwdbkbckbk'; //this should raise an AV!!!!!! 

end; 

procedure TTest.setNr; 
begin 
    aBtn.Caption := IntToStr(j) 
end; 

end. 

código de proyecto

unit Unit1; 

interface 

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

type 
    TForm1 = class(TForm) 
    Button1: TButton; 
    procedure Button1Click(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    private 
    public 
    nrthd:Integer; 
    acrit:TRTLCriticalSection; 
    procedure bla(); 
    procedure bla1(); 
    function bla2():boolean; 
    procedure onterm(Sender:TObject); 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.dfm} 

procedure TForm1.bla; 
begin 
try 
    bla1; 
except on e:Exception do 
    ShowMessage('bla '+e.Message); 
end; 
end; 

procedure TForm1.bla1; 
begin 
try 
    bla2 
except on e:Exception do 
    ShowMessage('bla1 '+e.Message); 
end; 
end; 

function TForm1.bla2: boolean; 
var ath:TTest; 
begin 
try 
    ath:=TTest.Create(true); 
    InterlockedIncrement(nrthd); 
    ath.FreeOnTerminate:=True; 
    ath.aBtn:=Button1; 
    ath.OnTerminate:=onterm; 
    ath.Resume; 
except on e:Exception do 
    ShowMessage('bla2 '+e.Message); 
end; 
end; 

procedure TForm1.Button1Click(Sender: TObject); 

begin 
// 
try 
    bla; 
    while nrthd>0 do 
    Application.ProcessMessages; 
except on e:Exception do 
    ShowMessage('Button1Click '+e.Message); 
end; 
ShowMessage('done with this'); 
end; 

procedure TForm1.FormCreate(Sender: TObject); 
begin 
nrthd:=0; 
end; 

procedure TForm1.onterm(Sender: TObject); 
begin 
InterlockedDecrement(nrthd) 
end; 

end. 

el propósito de esta aplicación es solo para saber dónde está atrapada la violación de acceso y cómo debe escribirse el código.
No puedo entender por qué en la línea "a [2]: = 'dbwdbkbckbk';" el AV no se levanta.

+1

No nos hizo el depurador ¿Te digo sobre la excepción? –

+0

No, nada en absoluto. – RBA

Respuesta

18

En Delphi 2005 - y, probablemente, más otras versiones - una excepción si se escapa del método Execute sin ser manipulados, entonces es capturado por la función que llamó Execute y se almacena en la propiedad del hilo FatalException. (Consulte en Classes.pas, ThreadProc). No se hace nada más con esa excepción hasta que se libere el subproceso, momento en el que también se libera la excepción.

Es su responsabilidad, por lo tanto, verificar esa propiedad y hacer algo al respecto. Puede verificarlo en el manejador OnTerminate del subproceso. Si no es nulo, el hilo finalizó debido a una excepción no detectada. Entonces, por ejemplo:

procedure TForm1.onterm(Sender: TObject); 
var 
    ex: TObject; 
begin 
    Assert(Sender is TThread); 
    ex := TThread(Sender).FatalException; 
    if Assigned(ex) then begin 
    // Thread terminated due to an exception 
    if ex is Exception then 
     Application.ShowException(Exception(ex)) 
    else 
     ShowMessage(ex.ClassName); 
    end else begin 
    // Thread terminated cleanly 
    end; 
    Dec(nrthd); 
end; 

No hay necesidad de funciones enclavadas para rastrear su conteo de hilos. Tanto su función de creación de hilo como su controlador de terminación siempre se ejecutan en el contexto del hilo principal. Los antiguos Inc y Dec son suficientes.

+0

+1. ¿Nunca he visto FatalException? ... oh, espera, todavía estamos en Delphi 5. –

+0

Ya no tengo el código fuente de esa versión, @Lieven. Si tiene 'AcquireExceptionObject', entonces usted puede imitar la nueva conducta' FatalException' usted mismo. –

+0

@Lieven: Creo que en D5 y D6, el método de ejecución del subproceso aún no estaba protegido ... Debes hacerlo tú mismo dentro de la anulación de Execute. –

12

Enhebrar es un lugar donde debe tragar excepciones.

La esencia del manejo de Excepciones en los hilos es que si desea que la excepción se muestre al usuario final, debe capturarla y pasarla al hilo principal donde se puede mostrar con seguridad.

Encontrará algunos ejemplos en este hilo EDN How to Handle exceptions in TThread Objects.

procedure TMyThread.DoHandleException; 
begin 
    // Cancel the mouse capture 
    if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0); 
    // Now actually show the exception 
    if FException is Exception then 
    Application.ShowException(FException) 
    else 
    SysUtils.ShowException(FException, nil); 
end; 

procedure TMyThread.Execute; 
begin 
    FException := nil; 
    try 
    // raise an Exception 
    raise Exception.Create('I raised an exception'); 
    except 
    HandleException; 
    end; 
end; 

procedure TMyThread.HandleException; 
begin 
    // This function is virtual so you can override it 
    // and add your own functionality. 
    FException := Exception(ExceptObject); 
    try 
    // Don't show EAbort messages 
    if not (FException is EAbort) then 
     Synchronize(DoHandleException); 
    finally 
    FException := nil; 
    end; 
end; 
0

También podemos volver a publicar FatalException. Reraising no parece lógico, pero si tiene un controlador central de excepción/error en el código y, y si lo que desea incluir excepciones hilo en que mechanisim, puede volver a subir en alguna situación poco frecuente:

procedure TForm1.onterm(Sender: TObject); 
var 
    ex: Exception; 
begin 
    Assert(Sender is TThread); 
    ex := Exception(TThread(Sender).FatalException); 
    if Assigned(ex) then 
    // Thread terminated due to an exception 
    raise ex; 
    Dec(nrthd); 
end; 
+1

¿Estás seguro de que esto funciona? ¿Qué pasa con la liberación de ese objeto de excepción? – dummzeuch

+1

Esto no funciona (al menos en XE2). Lo intenté y obtuve un diálogo de mensaje no modal seguido de un EOSError. –

+0

El controlador 'OnTerminate' es llamado por' Synchronize() 'dentro del contexto del hilo del trabajador después de que' Execute() 'sale. Si se produce una excepción dentro de 'Synchronize()', adquiere la excepción y la vuelve a plantear en el contexto del hilo del trabajador. Por lo tanto, generar una excepción dentro de 'OnTerminate' es algo malo. Para hacer esto correctamente, debería tomar manualmente la propiedad de 'FatalException', restablecer de alguna manera' TThread.FFatalException' a nil (para que 'TThread' no destruya el objeto), y volver a subirlo en el hilo principal usted mismo después de 'Synchronize()' ha salido. Se requieren hacks para tomar posesión –

Cuestiones relacionadas