Модераторы: Poseidon, Snowy, bems, MetalFan

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Exception в Thread 
V
    Опции темы
cemick
Дата 21.1.2009, 11:57 (ссылка) |    (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 416
Регистрация: 6.7.2006
Где: Санкт-Петербург

Репутация: 2
Всего: 6



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

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


Это сообщение отредактировал(а) cemick - 21.1.2009, 11:57
PM MAIL WWW   Вверх
Matematik
Дата 21.1.2009, 12:14 (ссылка) |   (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1027
Регистрация: 11.3.2006

Репутация: 17
Всего: 50



Цитата(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, могу и ошибаться


Это сообщение отредактировал(а) Matematik - 21.1.2009, 12:16
PM MAIL WWW ICQ   Вверх
CodeMonkey
Дата 21.1.2009, 12:16 (ссылка) |   (голосов:2) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



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


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
cemick
Дата 21.1.2009, 12:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 416
Регистрация: 6.7.2006
Где: Санкт-Петербург

Репутация: 2
Всего: 6



Спасибо, помогли.
PM MAIL WWW   Вверх
JSinx
Дата 4.2.2009, 23:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 71
Регистрация: 12.12.2006

Репутация: нет
Всего: 1



Цитата(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;


показывается еще раз окно с исключением, но потом уже совсем левые ошибки среды идут :((
PM MAIL   Вверх
CodeMonkey
Дата 5.2.2009, 11:26 (ссылка)  | (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



Код
  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 в главную форму и в обработчике сообщения уже выполнить все свои грязные действия.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
JSinx
Дата 5.2.2009, 23:39 (ссылка)  | (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 71
Регистрация: 12.12.2006

Репутация: нет
Всего: 1



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

  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;



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

Это сообщение отредактировал(а) JSinx - 8.2.2009, 17:31
PM MAIL   Вверх
CodeMonkey
Дата 6.2.2009, 10:00 (ссылка)   | (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



Вроде бы в этом примере всё чисто и красиво.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
SyCoDeR
Дата 6.2.2009, 10:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 2
Регистрация: 6.2.2009

Репутация: нет
Всего: нет



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

Ать-ать (бьёт по рукам). Нельзя так. Могли бы и сами догадаться. Ведь обычно проверкой 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;


PM MAIL   Вверх
CodeMonkey
Дата 6.2.2009, 14:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



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

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


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
SyCoDeR
Дата 6.2.2009, 14:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 2
Регистрация: 6.2.2009

Репутация: нет
Всего: нет



Во всяком сулчае в 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)
на 'Вызываем новое исключение.' будет два!! раза. Собственно, я думаю, что это еще одно проявление той же ошибки. Разобраться бы еще какой...

Это сообщение отредактировал(а) SyCoDeR - 6.2.2009, 14:49
PM MAIL   Вверх
CodeMonkey
Дата 6.2.2009, 15:09 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



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

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


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
MetalFan
Дата 6.2.2009, 16:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Аццкий Сотона
****


Профиль
Группа: Комодератор
Сообщений: 3815
Регистрация: 2.10.2006
Где: Moscow

Репутация: 62
Всего: 128



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

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

Это сообщение отредактировал(а) MetalFan - 6.2.2009, 16:38


--------------------
There are always someone smarter than you...
PM MAIL   Вверх
CodeMonkey
Дата 8.2.2009, 12:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1839
Регистрация: 24.6.2008
Где: Россия, Тверь

Репутация: 38
Всего: 89



Кто-то уже подсуетился: QC Report #71230.


--------------------
Опытный программист на C++ легко решает любые не существующие в Паскале проблемы.
PM MAIL WWW ICQ Skype GTalk Jabber   Вверх
MetalFan
Дата 8.2.2009, 13:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Аццкий Сотона
****


Профиль
Группа: Комодератор
Сообщений: 3815
Регистрация: 2.10.2006
Где: Moscow

Репутация: 62
Всего: 128



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


--------------------
There are always someone smarter than you...
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: Общие вопросы"
SnowyMetalFan
bemsPoseidon
Rrader

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Дельфи обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, MetalFan, bems, Poseidon, Rrader.

 
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1441 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.