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.
FatalException
property of the thread. – JohanOnTerminate
event whether itsFatalException
is assigned. But still if you want to notify the main thread you need to use some synchronization mechanism. – user532231