Механизм исключения потока 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 не поднимается.
4 ответа
В Delphi 2005 - и, возможно, в большинстве других версий - если исключение выходит из Execute
метод без обработки, то он перехватывается функцией, которая Execute
и хранится в потоке 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
достаточно.
Потоки - это одно место, где вы должны проглотить исключения.
Суть обработки исключений в потоках заключается в том, что если вы хотите, чтобы исключение было показано конечному пользователю, вы должны перехватить его и передать его в основной поток, где его можно безопасно показать.
В этой теме 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;
Переменная "а" не инициализирована! Это может указывать на ЛЮБУЮ возможную область памяти на вашем компьютере. Он может даже указывать на места, которые не существуют физически (хотя это спорный вопрос из-за системы виртуальной памяти).
Таким образом, если в вашей программе "a" случайно указывает на допустимый адрес памяти (я имею в виду адрес, которым владеет процесс), то ваш код будет записываться в этом месте без нарушения прав доступа. Я думаю, что вы должны хотя бы поставить "а" в NIL.
См. Комментарий Реми Лебо здесь: /questions/25272731/chto-oznachaet-narushenie-dostupa/25272739#25272739
И это также: почему неинициализированные указатели вызывают нарушения доступа к памяти, близкие к 0?
Мы также можем поднять FatalException. Ререйз выглядит нелогичным, но если в вашем коде есть центральный обработчик исключений / ошибок и если вы просто хотите включить исключения потоков в этот механизм, вы можете выполнить ререйз в некоторых редких ситуациях:
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;