Механизм исключения потоков Delphi
У меня есть дилемма о том, как потоки работают в delphi, и почему в момент, когда поток должен возбуждать исключение, исключение не отображается. Ниже приведен код с комментариями, может быть, кто-нибудь из вас объяснит мне, как этот поток или delphi управляет нарушениями доступа.
//код потока
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.
код проекта
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.
Цель этого приложения - только знать, где находится нарушение прав доступа, и как писать код.
Я не могу понять, почему в строке "a [2]: = 'dbwdbkbckbk';" AV не поднимается.
Ответы
Ответ 1
В Delphi 2005 - и, возможно, в большинстве других версий - если исключение выходит из метода Execute
без обработки, то оно улавливается функцией, которая называется Execute
и сохраняется в свойстве thread FatalException
. (Посмотрите в Classes.pas, ThreadProc
.) Ничего дальше с этим исключением пока поток не освободится, и в этот момент исключение также освобождается.
Это ваша ответственность, следовательно, проверить это свойство и что-то сделать. Вы можете проверить его в потоковом обработчике OnTerminate
. Если он не равен нулю, то поток прекращается из-за неперехваченного исключения. Итак, например:
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;
Нет необходимости в блокированных функциях для отслеживания количества потоков. Как ваша функция создания потока, так и ваш обработчик завершения всегда выполняются в контексте основного потока. Обычные старые Inc
и Dec
являются достаточными.
Ответ 2
Threading - это одно место, где вы должны проглатывать исключения.
Суть обработки Исключения в потоках состоят в том, что если вы хотите, чтобы исключение отображалось конечному пользователю, вы должны его захватить и передать в основной поток, где его можно безопасно отобразить.
В этой цепочке EDN вы найдете несколько примеров Как обрабатывать исключения в объектах TThread.
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;
Ответ 3
Мы также можем переделать FatalException. Reraising кажется нелогичным, но если у вас есть центральный обработчик исключений/ошибок в вашем коде, и если вы просто хотите включить исключения потоков в этот механизм, вы можете сделать ререйз в некоторой редкой ситуации:
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;