在另一个线程中引发异常

时间:2011-05-03 14:34:10

标签: delphi exception-handling thread-safety

如何在Delphi中的另一个线程中引发异常? 我有线程1和线程2,我想在线程1中引发异常并在线程2中捕获它。

修改

我现在可以看到我的初步解释令人困惑。我想要做的是从线程1初始化线程2中的异常引发。因此引发异常并在线程2中捕获,但此过程由线程1控制。 假设我有一个创建工作线程的主线程。我需要一种机制来优雅地从主线程中停止工作线程,但由于某些原因,这里无关紧要,我不能使用TThread.Terminate / Terminated模式。所以我认为如果我可以启动(注入?)从主线程中提取工作线程,那么这可以用作停止信号。

6 个答案:

答案 0 :(得分:5)

你可以从Rob Delphi thread exception mechanismfrom this Embarcadero article的答案中激发灵感。

答案 1 :(得分:3)

通知线程取消的方法是安排线程检查布尔标志的状态并对其做出响应。该标志由控制线程设置,然后工作线程执行中止所需的操作。你必须定期检查国旗的状态。

这样的解决方案将是内置的Terminated方法的重新实现,但是您声明不能使用Terminated。我认为这会让你陷入困境。螺纹不能安全可靠地用力终止,因此您需要一种合作方法。

我强烈建议您重新构建您的架构,以便使用Terminated是可行的。

答案 2 :(得分:3)

这是一段将异常引发到另一个线程的示例代码。它使用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 page for EnterCriticalSection提及:"If a thread terminates while it has ownership of a critical section, the state of the critical section is undefined."。这对我来说是一个惊喜,我直觉地期望关键部分在拥有线程终止时被“释放”,但显然不是这样。

答案 3 :(得分:2)

这是不可能的,德尔福并不重要。异常信息驻留在堆栈中,堆栈属于线程(每个线程都有自己的堆栈)。因此,您必须在同一个线程中引发和处理异常。


@Max:如果你在另一个线程中执行代码(使用Synchronize或Queue方法),那么代码引发的异常只能在同一个(不同的)线程中捕获。

线程A可能会引发&捕获异常,将异常对象传递给线程B,线程B重新引发异常,但线程B绝对不可能捕获线程A引发的异常,因为每个线程都有自己的堆栈。

答案 4 :(得分:0)

扩展并简化@ David的答案:我将公共错误消息和errorState属性添加到我的线程类。如果线程中发生异常,我会处理或吃它(取决于什么是合适的)并使用异常信息等设置错误属性。

主线程检查thread.onTerminate事件(在主线程中运行)中的线程类错误属性,并在必要时通知frontEnd / User,显示从线程返回的异常异常信息。

HTH

答案 5 :(得分:0)

制作所有帖子" message"处理线程,并且无法处理"消息只是生成一个类似异常的消息,传递给需要知道的任何其他线程/主线程。我在分布式多线程应用程序框架here中使用此体系结构。

相关问题