如何在不调用.Sync的情况下处理在AsyncCalls函数中抛出的异常?

时间:2012-09-11 04:38:36

标签: multithreading delphi exception-handling delphi-2010

当我使用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引用,并使用它来引发主线程的异常,而不是在每个帖子中复制该代码。 needsToGoSecond metod可能仍会首先进入异常,但它会保留异常并且循环会捕获它。

4 个答案:

答案 0 :(得分:1)

您声明以下内容:

  

我真的没有在主线程中调用Sync的任何地方。这样做也证明很难,因为我实际上有另一个调用Sync的线程。

     

...

     

我是否需要使用try-catch包装这些函数的内部并使用VCLSync自行获取主线程的异常?有没有更好的方法来检查某种主线程空闲循环中的异常?

在另一个线程中调用Sync将在那里引发异常。如果你不想在那里提出它,如果它必须在主线程中引发,那么就没有选择。您只需在异步过程中自己捕获任何未处理的异常。然后,您可以将它们排队到主线程,可能是通过调用TThread.Queue,发布Windows消息或某种类似的队列机制。另一种选择是使用VCLSync,如果您不介意此时的同步。

底线是从另一个线程调用Sync,并且需要在主线程上引发异常,这不是兼容的目标。你必须自己捕获异常并停止AsyncCalls处理它。

基本上,这只是对您当前方法的扩展。此时您的火灾通知主线程,而不是主线程同步。毕竟,我猜你正在使用异步方法,因为你不想从主线程同步。因此,扩展是您需要能够通知主线程的错误和异常,以及更正常的结果。

答案 1 :(得分:0)

根据要求,这是一个如何将线程内部的异常传递给主线程的示例。异常停止在线程中执行,然后我捕获错误并将其返回给用户。

这里有两个非常简单的类,一个包含在线程中运行的代码的worker类,以及一个线程类:

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 :(得分:0)

所以最后,我提出了以下适合我需求的解决方案,我只想让GUI显示任何异常消息而不管线程。如果你想更具体地管理错误,我会推荐大卫给出的答案。

首先,使用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循环还没有创建在主线程中引发的异常的副本。因为您似乎需要对raise命令进行排队,所以无法重新引发当前异常。您将获得访问冲突,因为它最希望在GUI到达时清理它。

答案 3 :(得分:-1)

好吧,自从我们来到inexact answers后,还有一个。

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节介绍Async。
  • 第2.1.1节完全涵盖了Async中的异常处理

有关OTL例外情况的更多信息:http://www.thedelphigeek.com/2011/07/life-after-21-exceptions-in.html

  • 它不使用Async,并且线程原语保持一致,所以它也应该适用于Async