5

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 that 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.

Max
  • 19,654
  • 13
  • 84
  • 122
  • 1
    The fact that you are asking this sends shivers down my spine. ;-) One thread should do one thing and be totally self-contained and not bother any other thread. – Nick Hodges May 03 '11 at 14:45
  • @Nick - but consider the situation when your thread crashes at unexpected situation and user needs to know about that. In that case you need to notify the main thread. The good way is IMHO to handle exceptions in the thread and when something happens, simply notify the main thread by posting a message about the error like is shown [in this example](http://edn.embarcadero.com/article/22411). –  May 03 '11 at 15:00
  • @daemon_X the mean thread will get notified via the `FatalException` property of the thread. – Johan May 03 '11 at 15:13
  • @Johan - more precisely, your thread is terminated and you need to check the thread's `OnTerminate` event whether its `FatalException` is assigned. But still if you want to notify the main thread you need to use some synchronization mechanism. –  May 03 '11 at 15:18
  • @Max - so you just want to pass some variable (with exception data) to the worker thread from the main one. –  May 03 '11 at 21:32
  • @daemon_x Actually I just want to pass a signal without any data. – Max May 04 '11 at 05:17

6 Answers6

5

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

Community
  • 1
  • 1
3

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Actually I want to do it the other way, I want to send a signal from the main thread to a worker thread. – Max May 03 '11 at 20:36
  • 1
    @Max, threads are not event-driven, you can't "send a signal" and have the thread react to that (unless the thread implements a message pump, but threads don't usually do that). All synchronization objects require cooperation from the thread itself, including Events and Semaphores. Since anything you'd do requires adding code to your existing thread, might as well add some `if Terminated then Exit;` statements and have your thread support the usual Terminate-Terminated concept. – Cosmin Prund May 04 '11 at 06:47
  • @Cosmin Prund Why do you think it's impossible to send a signal? It is possible in .Net via Thread.Abort even without cooperation from the thread code. I think it should be possible in Delphi too, – Max May 04 '11 at 06:59
  • @Max - you probably mean something like [TThread.Suspend](http://docwiki.embarcadero.com/VCL/en/Classes.TThread.Suspend), but avoid using it, because it's deprecated now. Anyway it stops your thread running immediately. –  May 04 '11 at 07:53
  • I know many of you is against using `TThread.Suspend`­ method (including embarcadero docs :) but still I think that nothing can happen if you'll control only one worker thread, which cannot be affected by another one (except `TThread.Resume`) and when you'll ensure to resume it when application terminates. Shortly act as a debugger. –  May 04 '11 at 08:06
  • @Max, see my answer. I implemented something similar to what `.NET` does, using your suggestion of stopping the thread, altering it's instruction pointer and restarting the thread. My answer contains full sample code. Enjoy. – Cosmin Prund May 04 '11 at 10:46
3

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.
Community
  • 1
  • 1
Cosmin Prund
  • 25,498
  • 2
  • 60
  • 104
  • Very clever indeed but rather impractical. This is deadlock bait. You should say so in your answer and recommend that nobody actually uses the code for real. – David Heffernan May 04 '11 at 11:43
  • @David, I implemented something very similar to what [.NET Thread.Abort()](http://msdn.microsoft.com/en-us/library/ty8d3wta.aspx) does, complete with the long list of potential problems listed for that method! Any thread should be somewhat prepared for an exception to pop up anytime, for example because of `out-of-memory` conditions. Since this pops the exception into the thread's stack, all code in `finally` sections should be executed, giving the thread a chance to clean up nicely. This is clearly much nicer then simply terminating the background thread. I don't think this is deadlock bait. – Cosmin Prund May 04 '11 at 12:10
  • 1
    @Cosmin No that's just not right. Your exception could be inserted in between a call to EnterCriticalSection and the Try/Finally that immediately follows it. So yes it is deadlock bait. – David Heffernan May 04 '11 at 12:18
  • @David, you edited while I was editing, I added a long disclaimer. – Cosmin Prund May 04 '11 at 12:23
  • @Cosmin Good, although you should mention that the code can cause deadlocks. My edit was to change Abrot to Abort. Would you like to make the correction? – David Heffernan May 04 '11 at 12:24
  • @David, I'm unpleasantly surprised, but you're right about `EnterCriticalSection`. MSDN says `"If a thread terminates while it has ownership of a critical section, the state of the critical section is undefined."`. – Cosmin Prund May 04 '11 at 12:26
  • I have implemented similar thing, although Get/SetThreadContext works just fine: var ctx: CONTEXT; SetThreadContext(aThread.Handle, ctx). I was thinking that maybe it is possible to do it in a less hackish way. – Max May 04 '11 at 16:27
  • @Max, it's not possible, and if simply using `ctx: _CONTEXT` worked for you it was plain luck. I'm not sure what you mean by "less hackish way": anything that changes `EIP` is an ugly hack, and you can't force the thread to run your code unless you change `EIP`. That moved out of the way, what remains is an elegant ugly hack. – Cosmin Prund May 05 '11 at 05:38
  • @Cosmin Prund I tought that maybe there's some kind of OS mechanism to do this without manipulating instruction pointer. – Max May 05 '11 at 06:49
  • @Max, the normal flow of the thread dictates what instruction runs next. Imagine a thread doesn't call a single OS function (example: tests if a large number is "prime"): how could the OS get it to raise an exception? The only solution is manipulating the instruction pointer. – Cosmin Prund May 05 '11 at 07:12
  • @Cosmin Prund The keyword is NORMAL flow. In my case it's not a normal flow. I want to terminate the thread execution by raising an exception. – Max May 05 '11 at 07:47
2

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.

kludg
  • 27,213
  • 5
  • 67
  • 118
  • It is possible to execute arbitrary code in another thread context. So I guess it would be possible to execute code which raises the exception. Why do you think it's impossible? – Max May 03 '11 at 14:57
  • What you describe in your comment, Max, is not what you asked for in your question. Your comment describes raising an exception in thread 2 *from* thread 1, whereas your question describes raising an exception in thread 1 and somehow transferring control to thread 2, which cannot happen without thread 1 catching the exception first and notifying thread 2 through the usual thread-communication mechanisms. But if you know how to inject code into another thread, then please feel free to answer this question yourself. – Rob Kennedy May 03 '11 at 15:19
  • @Max Please explain how to execute arbitrary code in another thread context. – David Heffernan May 03 '11 at 21:12
  • @David Heffernan I can think of suspending the thread and then changing the thread context so it points to the code we want to execute and then resuming the thread. – Max May 04 '11 at 05:14
  • 1
    that is a recipe for deadlock – David Heffernan May 04 '11 at 11:34
0

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

Vector
  • 10,879
  • 12
  • 61
  • 101
  • But you have to bind the thread's `OnTerminate` event to your own `TNotifyEvent` in the main thread to achieve it (as Rob pointed [here](http://stackoverflow.com/questions/3627743/delphi-thread-exception-mechanism/3627964#3627964)). And you don't need to add exception properties, you'll get one (`Exception` class) through the FatalException property. And events cannot run, they just occurs :) –  May 03 '11 at 16:37
  • @daemon_x 4 - true, as I see - but that works only if it is indeed a fatal Exception. As for events, I understand :-) . Of course really they are just functions that run like any other function - just you don't call them explicitely. Let's agree that they neither 'run' nor 'happen' - they 'fire'.... – Vector May 03 '11 at 21:08
  • IMHO any unhandled exception will cause the thread termination no matter how serious it is. Let's take a look at [documentation](http://docwiki.embarcadero.com/VCL/en/Classes.TThread.FatalException) (except the first sentence in description, it belongs to FreeOnTerminate property :) –  May 03 '11 at 21:46
0

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.

Misha
  • 1,816
  • 1
  • 13
  • 16
  • Why don't you advice the OP to use synchronisation objects? Message processing introduces a huge overhead. – mg30rg Sep 12 '14 at 12:05