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 包装这些函数的内部结构并使用 aVCLSync自己获取主线程的异常?有没有更好的方法来检查某种主线程空闲循环中的异常?

编辑 1

我的另一个想法是创建一个循环,其唯一工作是检查 IAscynCall 引用是否存在异常,并使用它向主线程引发异常,而不是在每个帖子中复制该代码。该needsToGoSecond方法仍然可能首先到达异常,但它会保持异常并且循环会在那里捕获它。

4

4 回答 4

1

您声明以下内容:

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

……

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

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

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

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

于 2012-09-12T15:47:37.260 回答
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 文章:

于 2012-09-11T23:32:30.773 回答
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 访问它时被清除。

于 2012-09-28T18:07:46.913 回答
-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 节准确介绍了 Async 中的异常处理

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

  • 它不使用异步,并且线程原语保持统一,所以它也应该适用于异步
于 2012-09-12T09:29:11.693 回答