开发者

Raise exception in another thread

How do I raise an exception in another thread in Delphi? I have thread 1 and thread 2 and I want to raise an exception in thread 1 and to catch it in thread 2.

EDIT

I can see now that my initial explanation is confusing. What I want to do is to INITIATE an exception raising in thread 2 from thread 1. So exception is raised and caught in thread 2, but this process is controlled from thread 1. Let's say th开发者_JS百科at I have a main thread which creates a worker thread. I need a mechanism to stop the worker thread from the main thread gracefully, but because of some reasons, which are irrelevant here I cannot use TThread.Terminate/Terminated pattern. So I thought that if I could initiate (inject?) an exceptin raising in the worker thread from the main thread, then that could be used as a stopping signal.


You can inspire from Rob's answer here Delphi thread exception mechanism or from this Embarcadero article.


Here's a sample piece of code that raises an exception into an other thread. It uses SuspendThread to stop the thread, GetThreadContext to read the thread's registers, alters EIP (the instruction pointer), uses SetThreadContext and then ResumeThread to restart the thread. It works!

UKilThread unit

Nicely packaged for reuse unit that provides the AbortThread() routine:

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.

Demo project

Here's how to use 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.

Disclaimer

Please make sure you understand what this code does before you actually use it. This is by no means a replacement for proper Terminate - Terminated logic (that is, cooperative thread shut-down), but it's a better alternative to TerminateThread(). This has been modeled after the .NET Thread.Abort() method. I have no idea how the actual .NET method was implemented but none the less read up on that because the potential problems of using this code are similar:

  • The method doesn't actually terminate the thread, it raises an EAbort -derived exception in the context of the thread. The thread's code might catch the exception. That's very unlikely because EAbort exceptions are not supposed to be handled.
  • The method might stop the thread at any time. It might stop the thread while it's handling a finally section or while setting up a new exception frame. Even if your thread uses proper try-finally blocks, it might cause memory or resource leaks if the exception is raised after a resource has been allocated but before the resource has been assigned to a variable.
  • The code might cause deadlocks if the thread is interrupted immediately after EnterCriticalSection and just before the try-finally that normally follows. The MSDN page for EnterCriticalSection mentions: "If a thread terminates while it has ownership of a critical section, the state of the critical section is undefined.". This came as a surprise to me, I'd intuitively expect the critical section to be "released" when the owning thread terminates, but apparently that's not so.


The way to signal your thread to cancel is to arrange for your thread to check the status of a boolean flag and respond to that. The flag is set by the controlling thread and then the worker thread does what is needed to abort. You must check the status of the flag regularly.

Such a solution would be a re-implementation of the built-in Terminated method, but you state that you can't use Terminated. I think this leaves you in a bind. Threads can't safely and reliably be terminated by force so you need a co-operative method.

I strongly advise you to re-work your architecture so that use of Terminated is viable.


That is impossible, and Delphi does not matter. Exception information reside in stack, and stack belongs to thread (each thread has its own stack). Consequently you must raise and handle exception in the same thread.


@Max: if you execute a code in a different thread (using Synchronize or Queue methods) then the exception raised by the code can only be caught in the same (different) thread.

It is possible that a thread A raises & catches the exception, passes the exception object to a thread B and the thread B re-raises the exception, but it is absolutely impossible for a thread B to catch the exception raised by thread A because each thread has its own stack.


Extending and perhaps simplifying @David's answer: I add public error message and errorState properties to my thread class. If an exception occurs in the thread, I handle or eat it (depending on what's appropriate) and set the error properties with the exception info etc.

The Main thread checks the thread class error properties in the thread.onTerminate event (which runs in main thread) and notifies frontEnd/User if necessary, showing exception exception info returned from the thread.

HTH


Make all your threads "message" processing threads, and have the failure to "process" a message just generate an exception-like message to be passed onto any other threads/main thread that need to know. I use this architecture in my distributed multi-threaded application framework here.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜