将异步任务带回主线程的正确方法是什么?

问题描述 投票:0回答:1

我正在为 Shopee API 开发一个 Wrapper 作为 Delphi 包,它是开源的,可以在我的 gh 个人资料中找到。我已经遇到了与此类似的问题,该问题已在here得到解答。但现在我已经看不到路了。

从这里开始:

constructor TShopeeContext.Create(AMode, AAPIKey, APartnerID: string);
begin

  if (AMode <> TEST_SHOPEE) and (AMode <> PRODUCTION_SHOPEE_BR) then
    raise Exception.Create('Mode Invalid for Context');

  FHost := AMode;
  FAPI_Key := AAPIKey;
  FPartnerID := APartnerID;

  UpdateInternalConfigFields;

  if FRefreshToken = '' then
    FHasRefreshToken := False
  else
    FHasRefreshToken := True;

  // Checks if the authorization has been granted and is younger than 365 days.
  if (not FAuthorizationGranted) or ((Now - FAuthorizationDate) > 365) then
  begin
    Authorize;
  end;

end;

Authorize;
调用之后,任何依赖于授权代码的指令都会陷入未定义行为,因为无法保证我有代码。我需要等待授权返回才能继续。我的问题源于这样一个事实:Authorize 创建一个对象并启动一个异步例程,一个 HTTP 侦听器:

procedure TShopeeContext.Authorize;
var
  Authorizator: TShopeeAuthorizator;
begin
  // Request Authorization;
  Authorizator := TShopeeAuthorizator.Create(FHost, FAPI_Key, FPartnerID, 8342);
  try
    Authorizator.AuthorizationRequest;
  finally
    Authorizator.Free;
  end;

end;

AuthorizationRequest
程序的实现如下:

procedure TShopeeAuthorizator.AuthorizationRequest;
begin
  // Obtem a assinatura da chamada
  FSignature := GetPublicSignString(API_PATH_AUTHORIZATION, FTimestamp);

  // Constroi os parametros
  FRequestParameters.AddItem('partner_id', FPartnerID);
  FRequestParameters.AddItem('timestamp', FTimeStamp);
  FRequestParameters.AddItem('sign', FSignature);
  FRequestParameters.AddItem('redirect', REDIRECT_URL_WS+':'+IntToStr(FPort));

  ShellExecute(0, 'open', PChar(GenerateRequestURL(API_PATH_AUTHORIZATION, FRequestParameters)), nil, nil, SW_SHOWNORMAL);

  FCatcherServer := TCatcherServer.Create();
  FCatcherServer.OnFieldsReady := FieldsReadyHandler;
  FCatcherServer.Listen(FPort);
end;

FieldsReadyHandler
是我旧问题的主题,当前版本是:

procedure TShopeeAuthorizator.FieldsReadyHandler(Sender: TObject; Code,
  AuthorizatedID: string);
begin
  // Handle Code, Auth Type and AuthorizatedID.
  FConfigurator := TConfiguratorFile.Create;
  try
    FConfigurator.SaveAuthorizationInfo(Code, AuthorizatedID, (Sender as TCatcherServer).AuthorizationType);
    FSuccess := True;
  finally
    FConfigurator.Free;
  end;

  TThread.Queue(nil, procedure
  begin
    Sender.Free;
  end);
end;

通常这是如何做到这一点的问题,但老实说,在这一点上,我认为我把它复杂化了,甚至不知道我应该做什么来克服这个问题。无论如何,如果我要重写,我可以看到我自己陷入了同样的位置,我的后续任务取决于异步任务来完成。我还尝试添加一个事件来通知上下文它已经获得了代码,但是(我的观点是)由于对象的释放方式,它会引发访问冲突。

抱歉,我无法为此综合一个 TL;DR,但如果我必须索引我的问题,输出将是:解决此类问题的最佳方法是什么以及如何在 pascal 中处理它?例如,在 JS 中,我会无限链接

.then()
直到例程不再需要异步部分。我还觉得如果我现在不学会处理它,它会成为一种常见的痛苦,因为请求是异步的。

提前致谢。

delphi asynchronous shopee
1个回答
0
投票

参考:等待任务完成

为此,请使用事件对象。事件对象 (System.SyncObjs.TEvent) 应在全局范围内创建,以便 它们可以像所有线程都可见的信号一样起作用。 当一个线程完成其他线程所依赖的操作时,它 调用 TEvent.SetEvent。 SetEvent 打开信号,因此任何其他 检查的线程将知道操作已完成。

授权完成后,设置事件:

procedure TShopeeAuthorizator.FieldsReadyHandler(Sender: TObject; Code,
  AuthorizatedID: string);
begin
  // Handle Code, Auth Type and AuthorizatedID.
  FConfigurator := TConfiguratorFile.Create;
  try
    FConfigurator.SaveAuthorizationInfo(Code, AuthorizatedID, (Sender as TCatcherServer).AuthorizationType);
    FSuccess := True;

    // Notify 
    Event1.SetEvent;

  finally
    FConfigurator.Free;
  end;

  TThread.Queue(nil, procedure
  begin
    Sender.Free;
  end);
end;

并在 TShopeeContext 中,使用 Event 等待授权完成,或超过给定的超时时间:

  if (not FAuthorizationGranted) or ((Now - FAuthorizationDate) > 365) then
  begin
    Event1.ResetEvent; { clear the event before }
    
    Authorize;

    if Event1.WaitFor(20000) <> wrSignaled then
      raise Exception; 
  end;       

备注:

  • 作为一个小建议,我不建议将授权代码放在 TShopeeContext 的构造函数中。相反,请将其移至单独的方法,并保持构造函数较小,以便它主要初始化字段。
  • 我还没有在自己的代码中使用TEvent。还存在其他方法来实现信令,这可能比 TEvent 有优势。
© www.soinside.com 2019 - 2024. All rights reserved.