Поднять исключение в другой теме - PullRequest
5 голосов
/ 03 мая 2011

Как вызвать исключение в другом потоке в Delphi? У меня есть поток 1 и поток 2, и я хочу вызвать исключение в потоке 1 и перехватить его в потоке 2.

EDIT

Теперь я вижу, что мое первоначальное объяснение сбивает с толку. Я хочу инициализировать исключение в потоке 2 из потока 1. Таким образом, исключение возникает и перехватывается в потоке 2, но этот процесс управляется из потока 1. Допустим, у меня есть основной поток, который создает рабочий поток. Мне нужен механизм, чтобы изящно остановить рабочий поток из основного потока, но по некоторым причинам, которые здесь не имеют значения, я не могу использовать шаблон TThread.Terminate / Termination. Поэтому я подумал, что если бы я мог инициировать (внедрить?) Повышение исключений в рабочем потоке из основного потока, то это можно было бы использовать в качестве сигнала остановки.

Ответы [ 6 ]

5 голосов
/ 03 мая 2011

Вы можете вдохновиться ответом Роба здесь Механизм исключения потока Delphi или из этой статьи Embarcadero .

3 голосов
/ 04 мая 2011

Вот пример кода, который вызывает исключение в другом потоке.Он использует SuspendThread для остановки потока, GetThreadContext для чтения регистров потока, изменяет EIP (указатель инструкции), использует SetThreadContext, а затем ResumeThread для перезапуска потока.Это работает!

Блок UKilThread

Удобно упакован для блока повторного использования, который обеспечивает подпрограмму AbortThread():

unit UKillThread;

interface

uses Classes, Windows, SysUtils;

procedure AbortThread(const Th: TThread);

implementation

// Exception to be raized on thread abort.
type EThreadAbort = class(EAbort);

// Procedure to raize the exception. Needs to be a simple, parameterless procedure
// to simplify pointing the thread to this routine.
procedure RaizeThreadAbort;
begin
  raise EThreadAbort.Create('Thread was aborted using AbortThread()');
end;

procedure AbortThread(const Th: TThread);
const AlignAt = SizeOf(DWORD); // Undocumented; Apparently the memory used for _CONTEXT needs to be aligned on DWORD boundary
var Block:array[0..SizeOf(_CONTEXT)+512] of Byte; // The _CONTEXT structure is probably larger then what Delphi thinks it should be. Unless I provide enough padding space, GetThreadContext fails
    ThContext: PContext;
begin
  SuspendThread(Th.Handle);
  ZeroMemory(@Block, SizeOf(Block));
  ThContext := PContext(((Integer(@Block) + AlignAt - 1) div AlignAt) * AlignAt);
  ThContext.ContextFlags := CONTEXT_FULL;
  if not GetThreadContext(Th.Handle, ThContext^) then
    RaiseLastOSError;
  ThContext.Eip := Cardinal(@RaizeThreadAbort); // Change EIP so we can redirect the thread to our error-raizing routine
  SetThreadContext(Th.Handle, ThContext^);
  ResumeThread(Th.Handle);
end;

end.

Демонстрационный проект

Вот как это сделатьиспользуйте AbortThread:

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Classes,
  Windows,
  UKillThread;

var Th: TThread;

type
  TTestThread = class(TThread)
  public
    procedure Execute;override;
  end;

{ TTestTrehad }

procedure TTestThread.Execute;
var N: Integer;
begin
  try
    N := 1;
    while not Terminated do
    begin
      WriteLn(N);
      Inc(N);
      Sleep(1000);
    end;
  except on E:Exception do
    WriteLn(E.ClassName + ' / ' + E.Message);
  end;
end;

begin
  Th := TTestThread.Create(False);
  WriteLn('Press ENTER to raize exception in Thread');
  ReadLn;
  AbortThread(Th);
  WriteLn('Press ENTER to exit');
  ReadLn;
end.

Отказ от ответственности

Пожалуйста, убедитесь, что вы понимаете, что делает этот код, прежде чем вы фактически его используете.Это ни в коем случае не замена для правильной логики Terminate - Terminated (то есть отключение кооперативного потока), но это лучшая альтернатива TerminateThread().Это было смоделировано после метода .NET Thread.Abort () .Я понятия не имею, как был реализован настоящий метод .NET, но тем не менее прочитал об этом, потому что потенциальные проблемы использования этого кода похожи:

  • Метод на самом деле не завершает поток,это вызывает EAbort -обработанное исключение в контексте потока.Код потока может перехватить исключение.Это очень маловероятно, поскольку EAbort исключения не должны обрабатываться.
  • Метод может остановить поток в любое время .Он может остановить поток во время обработки раздела finally или при настройке нового фрейма исключения.Даже если ваш поток использует надлежащие блоки try-finally, он может вызвать утечку памяти или ресурсов, если исключение возникает после выделения ресурса, но до назначения ресурса переменной.
  • Код может вызватьвзаимоблокировки, если поток прерывается сразу после EnterCriticalSection и непосредственно перед try-finally, который обычно следует.Страница MSDN для EnterCriticalSection упоминает: "If a thread terminates while it has ownership of a critical section, the state of the critical section is undefined.".Это стало для меня неожиданностью, я бы интуитивно ожидал, что критическая секция будет «освобождена», когда завершается поток-владелец, но, очевидно, это не так.
3 голосов
/ 03 мая 2011

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

Такое решение будет повторной реализацией встроенного метода Termination, но вы утверждаете, что не можете использовать Termination.Я думаю, что это оставляет вас в безвыходном положении.Потоки нельзя безопасно и надежно прекратить принудительно, поэтому вам нужен совместный метод.

Я настоятельно советую переделать вашу архитектуру, чтобы использование Termination было жизнеспособным.

2 голосов
/ 03 мая 2011

Это невозможно, и Delphi не имеет значения.Информация об исключениях находится в стеке, а стек принадлежит потоку (каждый поток имеет свой собственный стек).Следовательно, вы должны вызывать и обрабатывать исключение в том же потоке.


@ Max: если вы выполняете код в другом потоке (используя методы Synchronize или Queue), то исключение, вызванное кодом, может быть толькоперехватывается в том же (другом) потоке.

Возможно, что поток A вызывает и перехватывает исключение, передает объект исключения в поток B, и поток B повторно вызывает исключение, но это абсолютнопоток B не может перехватить исключение, вызванное потоком A, поскольку каждый поток имеет свой собственный стек.

0 голосов
/ 04 мая 2011

Создайте все ваши потоки для обработки сообщений, и у вас не получится «обработать» сообщение, просто сгенерируйте сообщение, похожее на исключение, которое будет передано любым другим потокам / главному потоку, которые необходимо знать. Я использую эту архитектуру в моей распределенной многопоточной среде приложений здесь .

0 голосов
/ 03 мая 2011

Расширение и, возможно, упрощение @ Ответ Дэвида: я добавляю общедоступное сообщение об ошибке и свойства errorState в свой класс потока. Если в потоке возникает исключение, я обрабатываю или ем его (в зависимости от того, что уместно) и устанавливаю свойства ошибки с информацией об исключении и т. Д.

Главный поток проверяет свойства ошибки класса потока в событии thread.onTerminate (которое запускается в основном потоке) и уведомляет frontEnd / User, если необходимо, показывая информацию об исключительной ситуации, возвращенную из потока.

НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...