如何处理AsyncCalls函数中抛出的异常而不调用.Sync()?

3
当我使用AsyncCalls时,如何最好地将异常传递到主线程?例如:
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;

我知道调用`asycCall.Sync`会为我抛出异常,但由于我当前的线程通知主线程已经更新了,我真的没有在主线程中调用`Sync`的地方。这样做也很困难,因为我实际上还有另一个线程在调用`Sync`,以确保一些东西设置好了,然后才获取第一个线程应该处理的资源。
我需要用try-catch包装这些函数的内部并使用`VCLSync`自己将异常传递到主线程吗?还是有更好的方法在某种主线程空闲循环中检查异常? 编辑1 我想到的另一个想法是创建一个循环,其唯一的工作是检查IAscynCall引用是否有异常,并使用它将异常提升到主线程,而不是在每个post中重复该代码。`needsToGoSecond`方法可能仍然会先遇到异常,但它将保留异常,循环将在那里捕获它。

你尝试过 OmniThreadsLibrary 吗?它也有异步调用,并且有许多关于异常处理的文章和演示。 - Arioch 'The
我确实研究过它,但是文档不足以让我开始,而AsyncCalls的用法非常简单和灵活。我想坚持使用它。 - Eric G
这是一个开源库,所以是的,我有代码:http://andy.jgknet.de/blog/bugfix-units/asynccalls-29-asynchronous-function-calls/ 我非常想看看你如何处理它。 - Eric G
我没有使用过TAsyncCalls,但我已经在自己的线程类中添加了异常处理,并且主线程能够访问其他线程中发生的任何异常。你有TAsyncCalls的代码吗?这样你就可以添加异常处理变量和方法了。如果有的话,我会尝试在答案中总结我所做的事情... - James L.
1
那是什么?任务如何等待自己?试图自我死锁???asyncCall := TAsyncCalls.Invoke(procedure begin //initial stuff asyncCall.Sync(); - Arioch 'The
显示剩余2条评论
4个回答

1
您提到了以下内容:
“我真的没有在主线程中调用Sync的任何地方。这样做也很困难,因为我实际上有另一个线程正在调用Sync。”
“我需要用try-catch包装这些函数的内部,并使用VCLSync将异常自己传递给主线程吗?是否有更好的方法在某种主线程空闲循环中检查异常?”
在其他线程中调用Sync会在那里引发异常。如果您不希望在那里引发异常,并且必须在主线程中引发异常,则没有选择。您只需在异步过程中自己捕获任何未处理的异常。然后,您可以通过调用TThread.Queue、发布Windows消息或某些类似的队列机制将它们排队到主线程。如果您不介意在那个点进行同步,另一种选择是使用VCLSync。
底线是,在另一个线程中调用Sync并需要在主线程中引发异常是不兼容的目标。因此,您必须自己捕获异常并停止AsyncCalls处理它。

实质上这只是您目前方法的扩展。目前,您将消防通知发送到主线程,而不是使主线程同步。毕竟,我猜您正在使用异步方法,因为您不想从主线程同步。因此,扩展就在于您需要能够通知主线程出现错误和异常,以及更正常的结果。


我想知道在OTL中这样做是否会引起竞态条件。虽然它是兼容的,但可能不需要同步。 - Arioch 'The

0
最终,我想出了以下解决方案,它适合我的需求,即我只想让GUI显示任何异常消息,而不管线程如何。如果您想更加详细地管理错误,我建议使用David提供的答案。
首先,使用asyncCall.Sync()是不正确的。现在,我正在使用TEvent对象等待实际发生的事件。线程可以继续进行其他工作,而不会使等待线程等待时间过长。
其次,我现在正在使用循环捕获发生的异常,并将错误同步回我的主线程。例如,一个线程可能看起来像这样:
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;

还有一个线程在其他地方捕获并抛出异常:

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;

看起来异常必须由VCLInvoke(或TThread.Queue)调用而不是VCLSync(或TThread.Synchronize)调用抛出。在同步时,我认为外部的AsyncCall捕获了异常并阻止了GUI显示。此外,我的catch循环尚未创建异常的副本以在主线程中引发。因为似乎需要排队引发命令,所以不能重新引发当前异常。当GUI到达时,它很可能已被清理,你将收到一个访问冲突。


0

根据要求,这里是一个示例,展示我如何将线程中的异常传递到主线程。当线程中发生异常时,它会停止执行,然后我捕获错误并将其返回给用户。

这里有两个非常简单的类,一个工作类包含在线程中运行的代码,另一个是线程类:

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;

然后我的主线程代码创建一个 worker 对象,并将其传递给线程的构造函数以执行。执行完成后,主线程检查并重新引发任何异常。
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;

您也可以查看这篇EDN文章:


2
这有点弱。首先它与AsyncCalls无关。更重要的是,你重新引发异常的方式很差。使用AcquireExceptionObject获取实际的异常对象,稍后可以重新引发。使用ExceptAddr以便在适当的地址重新引发。请注意,这有点毫无意义,因为TThread已经为您完成了此操作。请参阅FatalException - David Heffernan
@DavidHeffernan +1 因为你的教导。我已编辑示例,使用了 AcquireExceptionObjectExceptAddr。将研究 FatalException... - James L.

-1

既然我们谈到了不精确的答案,那么这里再介绍一个。

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

论坛(作者非常负责):http://otl.17slon.com/forum/

异步示例:http://www.thedelphigeek.com/2012/07/asyncawait-in-delphi.html

书籍示例:http://samples.leanpub.com/omnithreadlibrary-sample.pdf

  • 第2.1节介绍了异步。
  • 第2.1.1节详细介绍了异步中的异常处理。

OTL中有关异常的更多信息:http://www.thedelphigeek.com/2011/07/life-after-21-exceptions-in.html

  • 它不使用异步,线程原语保持一致,因此也适用于异步

1
我认为这应该作为注释更好。AsyncCalls确实处理异常,而且这个问题是完全可以回答的。建议使用完全不同的框架似乎有些愚蠢。是的,OTL很好,但AsyncCalls也非常棒。 - David Heffernan
嗯,David,熟悉异步编程的人可以给出更有针对性的答案,确实如此。但是一段时间内没有这样的人。 - Arioch 'The

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接