问题
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.
回答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.
回答2:
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:
- How to handle exceptions in TThread objects
回答3:
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.
回答4:
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
来源:https://stackoverflow.com/questions/12362851/how-to-handle-exceptions-thrown-in-asynccalls-function-without-calling-sync