3

When I am using AsyncCalls, how do you best get the exception into the main thread? For example:

procedure TUpdater.needsToGoFirst();
begin
  asyncCall := TAsyncCalls.Invoke(procedure 
  begin 
    //some stuff
    if (possiblyTrueCondition = True) then
      raise Exception.Create('Error Message');
    //more stuff
    TAsyncCalls.VCLSync(procedure 
    begin 
      notifyUpdates(); 
    end);
  end);
end;

procedure TUpdater.needsToGoSecond();
begin
  asyncCall2 := TAsyncCalls.Invoke(procedure 
  begin 
    //initial stuff
    asyncCall.Sync();
    //stuff that needs to go second
    TAsyncCalls.VCLSync(procedure 
    begin 
      notifyUpdates(); 
    end);
  end);
end;

I know calling asycCall.Sync will throw the exception for me, but due the the way I'm currently having my thread notify the main thread that updates have been made, I really don't have anywhere that I'm calling Sync in the main thread. Doing so also proves difficult because I actually have another thread that is calling Sync to make sure some things are set before acquiring resources the first thread should process first.

Do I need to wrap the innards of these functions with a try-catch and use a VCLSync to get the exception to the main thread myself? Is is there a better way to check for exceptions in a main thread idle loop of some kind?

Edit 1

Another thought I had was to create a loops whose only job is to check the IAscynCall references for exceptions and use that to Raise the exception to the main thread, rather than duplicating that code in every post. The needsToGoSecond metod may still get to the exception first, but it will then hold the exception and the loop would catch it there.

Eric G
  • 3,427
  • 5
  • 28
  • 52
  • did you tried OmniThreadsLibrary ? It also has Async call and has a number of articles and demos about exception handling – Arioch 'The Sep 11 '12 at 06:48
  • I did look into it, but the documentation wasn't enough to get me going, where as the usages of AsyncCalls was very simple and flexible. I'd like to stick with it. – Eric G Sep 11 '12 at 17:59
  • It is an open source library, so yes, I do have the code: http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/ I would be very interested to see you you handled it. – Eric G Sep 11 '12 at 22:58
  • (sorry, shouldn't have deleted this comment) I haven't used TAsyncCalls, but I did add exception handling it to my own threading class and the main thread is able to access any exceptions that occur in the other threads. Do you have the code to TAsyncCalls so you can added the exception handling variables and methods? If so, I'll try and summarize what I did in an answer... – James L. Sep 11 '12 at 22:58
  • about OTL documentation - see 2.1.1 at http://samples.leanpub.com/omnithreadlibrary-sample.pdf You can also ask on OTL forum, he is keeping an eye there and usualyl is very helpful – Arioch 'The Sep 12 '12 at 09:17
  • 1
    What is that ? how can a task wait upon itself ? attempt at self-deadlock ??? *asyncCall := TAsyncCalls.Invoke(procedure begin //initial stuff asyncCall.Sync();* – Arioch 'The Sep 12 '12 at 16:05
  • When i simplified the code for posting, I ended up calling both thread holders the same thing. Fixed that. – Eric G Sep 12 '12 at 17:34

4 Answers4

1

You state the following:

I really don't have anywhere that I'm calling Sync in the main thread. Doing so also proves difficult because I actually have another thread that is calling Sync.

....

Do I need to wrap the innards of these functions with a try-catch and use a VCLSync to get the exception to the main thread myself? Is there a better way to check for exceptions in a main thread idle loop of some kind?

Calling Sync in the other thread will raise the exception there. If you don't want it raised there, and if it must be raised in the main thread, then there is no option. You simply have to catch any unhandled exceptions yourself, in the async procedure. You can then queue them off to the main thread, perhaps by calling TThread.Queue, posting a Windows message, or some similar queue mechanism. Another option would be to use VCLSync if you don't mind the synchronisation at that point.

The bottom line is that calling Sync from another thread, and needing the exception to be raised on the main thread, are not compatible goals. Ergo you must catch the exception yourself and stop AsyncCalls dealing with it.

Essentially this is just a broadening of your current approach. At the moment your fire notifications to the main thread, rather than have the main thread sync. After all, I guess you are using an asynchronous approach because you don't want to sync from the main thread. So, the broadening is that you need to be able to notify the main thread of errors and exceptions, as well as more normal outcomes.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • I wonder if doing that in OTL would cause race condition or not. There it is compatible, yet probably synchronization is not warranted. – Arioch 'The Sep 12 '12 at 15:58
0

As requested, here is an example of how I pass an exception inside of a thread to the main thread. An exception stops execution in the thread, and then I trap the error and return it to the user.

Here are two very simple classes, a worker class that includes the code to run in a thread, and the thread class:

type
  TMyWorker = class
  private
    FExceptionObject: Pointer;
    FExceptionAddress: Pointer;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure _Execute;
    procedure GetException;
  end;

implementation

constructor TMyWorker.Create;
begin
  inherited Create;
  FExceptionObject := nil;
  FExceptionAddress := nil;
end;

procedure TMyWorker._Execute;
begin
  try
    // run code
  except
    FExceptionObject := AcquireExceptionObject; // increments exception's refcnt
    FExceptionAddress := ExceptAddr;
  end;
end;

procedure TMyWorker.GetException;
begin
  if Assigned(FExceptionObject) then
    raise Exception(FExceptionObject) at FExceptionAddress; // decrements exception's refcnt
end;

destructor TMyWorker.Destroy;
begin
  if Assigned(FExceptionObject) then
  begin
    ReleaseExceptionObject; // decrements exception's refcnt
    FExceptionObject := nil;
    FExceptionAddress := nil;
  end;
  inherited;
end;

.

type
  TMyThread = class(TThread)
  private
    FWorker: TMyWorker;
  protected
    procedure Execute; override;
  public
    constructor Create(Worker: TMyWorker);
  end;

implementation

procedure TMyThread.Execute;
begin
  FWorker._Execute;
end;

constructor TMyThread.Create(Worker: TMyWorker);
begin
  FWorker := Worker;
  FreeOnTerminate := False;
  inherited Create(False);
end;

Then my main thread code creates a worker objects and passes it to the constructor of the thread to execute. When execution completes, the main thread checks for and re-raises any exceptions.

var
  myWorker: TMyWorker;
begin
  myWorker := TMyWorker.Create;
  try
    with TMyThread.Create(myWorker) do
    begin
      WaitFor; // stop execution here until the thread has finished
      Free;    // frees the thread object
    end;
    myWorker.GetException; // re-raise any exceptions that occurred while thread was running
  finally
    FreeAndNil(myWorker);
  end;
end;

You might also look at this EDN article:

James L.
  • 9,384
  • 5
  • 38
  • 77
  • 2
    This is rather weak. For a start it isn't related to AsyncCalls. What's more the way you re-raise is poor. Use `AcquireExceptionObject` to obtain the actual exception object which you can later re-raise. Use `ExceptAddr` so that you can re-raise at the appropriate address. Note that it's all a little pointless because `TThread` already does this for you. See `FatalException`. – David Heffernan Sep 12 '12 at 11:35
  • @DavidHeffernan +1 for teaching. I edited the example to use `AcquireExceptionObject` and `ExceptAddr`. Will look at `FatalException`... – James L. Sep 12 '12 at 12:35
0

So in the end, I've come up with the following solution that suits my needs, which is I simply want the GUI to show any exception message regardless of thread. If you want to manage the errors more particularly, I would recommend the answer given by David.

First, using asyncCall.Sync(); was incorrect. I am now using TEvent objects to wait for the actually event in question to occur. Threads can then continue with other work without make waiting threads wait longer than needed.

Second, I am now using a loop to catch exceptions that occur and sync the errors back to my main thread. For example, one thread may look like this:

procedure TUpdater.needsToGoSecond();
begin
  fAsyncThreads.Add(TAsyncCalls.Invoke(procedure 
  begin 
    //initial stuff
    myEvent.Wait();
    //stuff that needs to go second
    if (possiblyTrueCondition = True) then
      raise Exception.Create('Error Message');
    TAsyncCalls.VCLSync(procedure 
    begin 
      notifyUpdates(); 
    end);
  end));
end;

And I have another thread catching and raising the exceptions elsewhere:

procedure TUpdater.catchExceptions();
begin
  fAsyncCatchExceptions := TAsyncCalls.Invoke(procedure
  var
    asyncThread: IAsyncCall;
    errorText: string;
  begin
    while(true)do
    begin
      for asyncThread in fAsyncThreads do
      begin
        if(Assigned(asyncThread)) and (asyncThread.Finished)then
        begin
          try
            asyncThread.Sync();
          except on E: Exception do
            begin
              errorText := E.Message;
              TAsyncCalls.VCLInvoke(procedure begin raise Exception.Create(errorText); end);
            end;
          end;
          fAsyncThreads.Remove(asyncThread);
        end;//if
      end;//for
      Sleep(2000);
    end;//while
  end);
end;

It appears that the exception must be thrown by a VCLInvoke (or TThread.Queue) call rather than a VCLSync (or TThread.Synchronize) call. When synchronizing, I think the outer AsyncCall catches the exception and prevents it from being shown by the GUI. Also, my catch loop does not as yet create a copy of the exception to raise in the main thread. Because you seem to need to queue the raise command, you cannot re-raise the current exception. You'll get an access violation as it will most like be cleaned up by the time the GUI gets to it.

Eric G
  • 3,427
  • 5
  • 28
  • 52
-1

Well, since we came to inexact answers, here is one more.

OmniThreadLibrary: http://otl.17slon.com/

Forum (and author is very responsive there): http://otl.17slon.com/forum/

Async example: http://www.thedelphigeek.com/2012/07/asyncawait-in-delphi.html

Book sample: http://samples.leanpub.com/omnithreadlibrary-sample.pdf

  • Section 2.1 introduces Async.
  • Section 2.1.1 exactly covers exceptions handling in Async

More on exceptions in OTL: http://www.thedelphigeek.com/2011/07/life-after-21-exceptions-in.html

  • it does not use Async namely, and threading primitives are kept uniform, so it should apply for Async as well
Community
  • 1
  • 1
Arioch 'The
  • 15,799
  • 35
  • 62
  • 1
    I feel that this would be better as a comment. AsyncCalls does handle exceptions and the question is perfectly answerable. Suggesting a completely different framework seems rather obtuse. Yes, OTL is good, but AsyncCalls is pretty darn tasty too. – David Heffernan Sep 12 '12 at 15:09
  • Well, David, person experienced in async can make a more targeted answer, truly so. But there was no such person for a while. – Arioch 'The Sep 12 '12 at 15:33