Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Exception в Thread


Автор: cemick 21.1.2009, 11:57
Помогите разобратся, в процесс работы с потоками появился вопрос:

 Что происходит с исключениями возбужденными в Tthread.Execute? 

Автор: Matematik 21.1.2009, 12:14
Цитата(cemick @  21.1.2009,  11:57 Найти цитируемый пост)
 Что происходит с исключениями возбужденными в Tthread.Execute? 

Поток завершается.
Далее исключение можно отловить в событии Tthread.OnTerminate()

Код

{...}
Thread := Thread.Create();
Thread.OnTerminate := ThreadDone;
{...}
procedure TMainForm.ThreadDone(Sender: TObject);
var E : Exception;
begin
  E := (Sender as TThead).FatalException;
  if E<>nil then
    ShowMessage(E.ClassName+' '+E.Message);
end;

AFAIK, могу и ошибаться

Автор: CodeMonkey 21.1.2009, 12:16
Всё верно.
+ ещё:
Если свойство потока FreeOnTerminate ложно, то FatalException можно ещё прочитать после завершения потока откуда угодно - не обязательно из OnTerminate - пока объект потока не будет удалён.

Автор: cemick 21.1.2009, 12:54
Спасибо, помогли.

Автор: JSinx 4.2.2009, 23:44
Цитата(CodeMonkey @ 21.1.2009,  12:16)
Всё верно.
+ ещё:
Если свойство потока FreeOnTerminate ложно, то FatalException можно ещё прочитать после завершения потока откуда угодно - не обязательно из OnTerminate - пока объект потока не будет удалён.

Странно, но у меня он равен nil, даже если возникало исключение, хотя при создании потока делаю FreeOnTerminate := false;

Собственно мне нужно сделать обработку исключения из потока в главной форме, но при вызове raise у меня выводит кучу ошибок.

т.е. вот такой код:
Код

{...}
Thread := Thread.Create();
Thread.OnTerminate := ThreadDone;
{...}
procedure TMainForm.ThreadDone(Sender: TObject);
var E : Exception;
begin
  E := (Sender as TThead).FatalException;
  if E<>nil then
    raise E;
end;


показывается еще раз окно с исключением, но потом уже совсем левые ошибки среды идут :((

Автор: CodeMonkey 5.2.2009, 11:26
Код
  E := (Sender as TThead).FatalException;
  if E<>nil then
    raise E;


Ать-ать (бьёт по рукам). Нельзя так. Могли бы и сами догадаться. Ведь обычно проверкой FatalException никто не заморачивается. Что это значит? А то, что по-умолчанию FatalException освобождается при удалении экземпляра TThead. 
С другой стороны, передавая экземпляр объекта в raise вы отдаёте управление сроком его жизни в рамки стандартного механизма исключений.
Вот у вас и подрались два механизма.

Правильный вариант (например):

Код
  E := (Sender as TThread).FatalException;
  if E<>nil then
    raise Exception.Create(E.Message);


Или (более вкусный, но грязный):

Код
  E := (Sender as TThread).FatalException;
  if E<>nil then
  begin
    PPointer(@(Sender as TThread).FatalException)^ := nil;  // "уговаривам" экземпляр TThread "забыть" про исключение
    // сейчас ответственность за E лежит на нас
    raise E;               // <- а сейчас - уже на механизме исключений
  end;


Добавлено через 4 минуты и 26 секунд
Да, и кстати, возбуждать исключение в OnTerminate - крайне плохая идея. Если вы посмотрите на исходники TThread, то увидите:

Код
...
    Thread.DoTerminate;                    // <- здесь вы возбудили исключение
    // при исключении все команды ниже пропускаются
    Thread.FFinished := True;
    SignalSyncEvent;                          
    if FreeThread then Thread.Free;   // <- объект потока утёк
    EndThread(Result);
...


Правильный вариант - сделать PostMessage в главную форму и в обработчике сообщения уже выполнить все свои грязные действия.

Автор: JSinx 5.2.2009, 23:39
Увидел в примере какой то книжки вот такую обработку исключений в потоках:
Код

  TTestThread = class(TThread)
  private
    fEx : Exception;
    procedure QueryError;
  protected
    procedure Execute; override;
  end;

procedure TTestThread .Execute;
begin
  inherited;
  CoInitializeEx( nil, COINIT_MULTITHREADED );  // - это нужно для работы экселя
  try
    ... всякое разное ...
  except
    fEx := ExceptObject as Exception;
    Synchronize( QueryError );
  end;
end;

procedure TTestThread .QueryError;
begin
  Application.OnException( Self, fEx );
end;



Такой метод чемнить плох?

Автор: CodeMonkey 6.2.2009, 10:00
Вроде бы в этом примере всё чисто и красиво.

Автор: SyCoDeR 6.2.2009, 10:26
Вступлю в дискуссию, ибо сам столкнулся с этой проблемой.
Цитата

Ать-ать (бьёт по рукам). Нельзя так. Могли бы и сами догадаться. Ведь обычно проверкой FatalException никто не заморачивается. Что это значит? А то, что по-умолчанию FatalException освобождается при удалении экземпляра TThead. 
С другой стороны, передавая экземпляр объекта в raise вы отдаёте управление сроком его жизни в рамки стандартного механизма исключений.
Вот у вас и подрались два механизма.

Тут вы видимо правы.
Но вот это
Код

  E := (Sender as TThread).FatalException;
  if E<>nil then
  begin
    PPointer(@(Sender as TThread).FatalException)^ := nil;  // "уговаривам" экземпляр TThread "забыть" про исключение
    // сейчас ответственность за E лежит на нас
    raise E;               // <- а сейчас - уже на механизме исключений
  end;

не работает.
И это
Код

  E := (Sender as TThread).FatalException;
  if E<>nil then
    raise Exception.Create(E.Message);

к сожалению тоже. 
Вообще совершенно любая попытка вызвать исключение в обработчике OnTerminate даже никак не связанное с исходным - например так
Код

procedure TForm2.OnThreadTerminate(Sender: TObject);
begin
  Raise Exception.Create('Error!');
end;

Вызывает падение программы.
К сожалению, я не достаточно понимаю механизмы работы исключений  - они достаточно сложны и абсолютно не документированы. А исходники написаны на ASM'е и, что хуже, опираются на недокументированные структуры данных.
Если кто-нибудь в деталях объяснит, почему нельзя делать Raise в OnTerminate буду чрезвычайно благодарен. smile 

А пока - обходной путь:

Код

procedure TForm2.OnThreadTerminate(Sender: TObject);
begin
  If Assigned(Sender) and (Sender is TThread) and Assigned((Sender as TThread).FatalException) Then
    Application.ShowException(Exception((Sender as TThread).FatalException));
end;


Автор: CodeMonkey 6.2.2009, 14:04
Цитата(SyCoDeR @  6.2.2009,  10:26 Найти цитируемый пост)
Вызывает падение программы.

Это http://delphikingdom.ru/asp/answer.asp?IDAnswer=65697.
Система завершает всё приложение целиком, если в любом из его потоков возникает необработанное исключение. Системная функция CreateThread создаёт голый поток - вы обязаны сами заключать его код в try/except. BeginThread - это обёртка вокруг CreateThread, которая (помимо всего прочего) устанавливает фрейм исключения. По исключению в потоке должен вызываться обработчик исключений из SysUtils (если я правильно помню). Проблема в том, что в некоторых версиях Delphi это поведение реализовано некорректно. Ну а TThread просто использует BeginThread для вызова функции Execute. 
Поскольку OnTerminate вызывается вне глобального обработчика исключений на Execute, то и исключение в нём ничем не обрабатывается, кроме оболочки BeginThread. Которая, как уже было сказано, работает не всегда.
Соответственно, ваше приложение может работать (показывая сообщение об ошибке) или вылетать - смотря в какой версии Delphi оно скомпилировано. +1 причина не делать ничего такого в OnTerminate ;)

Автор: SyCoDeR 6.2.2009, 14:48
Во всяком сулчае в CodeGear™ Delphi® 2007 for Win32®  Version 11.0.2902.10471 гарантированно вылетает.
Если кто-то может протестить на других версиях - вот код:
Код

unit Unit3;

interface

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

type
  TForm3 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    procedure OnThreadTerminate(Sender: TObject);
  end;

  TErrorThread = class(TThread)
  public
    procedure Execute; override;
  end;

var
  Form3: TForm3;

implementation

{$R *.dfm}

procedure TForm3.Button1Click(Sender: TObject);
begin
  With TErrorThread.Create(True) do
    begin
      FreeOnTerminate := True;
      OnTerminate := OnThreadTerminate;
      Resume;
    end;
end;

procedure TForm3.OnThreadTerminate(Sender: TObject);
begin
  Raise Exception.Create('Вызываем новое исключение.');
end;

{ TErrorThread }

procedure TErrorThread.Execute;
begin
  Raise Exception.Create('Вызвали исключение!');
end;

end.



Обратите внимание, вылет в дебагер (разумеется, если в опциях включено Stop on delphi exceptions)
на 'Вызываем новое исключение.' будет два!! раза. Собственно, я думаю, что это еще одно проявление той же ошибки. Разобраться бы еще какой...

Автор: CodeMonkey 6.2.2009, 15:09
Цитата(SyCoDeR @  6.2.2009,  14:48 Найти цитируемый пост)
Собственно, я думаю, что это еще одно проявление той же ошибки. Разобраться бы еще какой...

Вам не всё ли равно? Возбуждение исключения в OnTerminate является плохой идеей. Я не знаю, какими ещё словами вам это сказать.

Автор: MetalFan 6.2.2009, 16:28
механизм вызова метода, назначенного св-ву OnTerminate можно подсмотреть в VCL.
код в OnTerminate через механизм синхронизации выполняется в контексте главного потока, НО обрабатываться исключение должно в контексте потока, вызвавшего этот метод через Synchronize
да и вообще вызов необработанного исключения в любом потоке приведет к падению программы...

Добавлено @ 16:36
кстати, знает ли общественность об еще одном механизме вызова метода в контексте осн.потока VCL? см. TThread.Queue

Автор: CodeMonkey 8.2.2009, 12:08
Кто-то уже подсуетился: http://qc.codegear.com/wc/qcmain.aspx?d=71230.

Автор: MetalFan 8.2.2009, 13:03
да это не бага, это нормальное и логичное поведение, если посмотреть код ThreadWrapper в classes.pas

Автор: CodeMonkey 8.2.2009, 19:50
Так я и не спорил smile  Просто поражает способность людей всё делать не так как надо, а как им охота.

Автор: JSinx 10.2.2009, 19:44
Попытаюсь подвести итог smile. Как я понял, в итоге получилось 2 варианта:

Цитата

сделать PostMessage в главную форму и в обработчике сообщения уже выполнить все свои грязные действия.


и "вариант из книги"
Код

procedure TTestThread .QueryError;
begin
  Application.OnException( Self, Ex );
end;


Какой же из них тогда более правильный?

P.S. За "вариант из книги" почему-то проминусили smile



Автор: CodeMonkey 10.2.2009, 20:28
>>> Какой же из них тогда более правильный?
Оба правильные.
Просто работают по-разному. Хотя сильно принципиальной разницы между ними нет.

Автор: cemick 4.2.2013, 12:31
На правах автора темы подниму из небытия. Интересно, что если в не основном потоке будет 
      try
         // Код потока
      except
        // Сохраняем исключение в поле 
        FExceptionObject := AcquireExceptionObject();
      end;


А в главном потоке сделаем:
     raise FThread.ExceptionObject ;

Как мне видеться такой код чист и должен работать или я ошибаюсь?

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)