【问题标题】:Delphi - Asynchronous Datasnap Method CallsDelphi - 异步 Datasnap 方法调用
【发布时间】:2019-01-08 10:03:51
【问题描述】:

我正在编写一个 Datasnap 应用程序,在客户端/服务器之间使用 TCP 连接,服务器连接到 SQL 服务器。

服务器 有一个数据模块 DM1,其中包含所有数据集查询和 SQL 连接。 DM1 还具有 REST 请求/客户端/响应组件。

DM1 有一个带有 ID 参数的公开函数 PostDataAsync:从数据集生成 json,然后 HTTP 将其发布到 RESTFul 服务。它返回回调 arg 中发布失败的记录数。

这个 DM1 的 DSServer 是“Invocation”。

调用服务器类型应确保每个服务器方法调用都有自己的DB连接、数据集、Rest组件,不会有多个调用相互干扰数据(如果添加并行线程)。

procedure TServerMethods1.postCustOrderHistAsync(CustomerID: String; callback: TDBXcallback);
var
  jsonObject: TJSONObject;
  CallbackValue: TJsonValue;
  errors: Integer;
begin
  errors := postCustOrderHist(CustomerID); //takes time to post, returns num of failed records
  jsonObject := TJSONObject.create;
  jsonObject.AddPair(tjsonpair.create('errors', errors.ToString));
  CallbackValue := callback.Execute(jsonObject);
end;

Client有一个按钮调用服务器方法PostDataAsync,ID为param,还有一个回调函数“ShowNotification”(使用windows通知中心显示Post通知状态)。

目前,应用程序的工作方式如下:客户端同步调用服务器函数,即主线程等待服务器函数完成HTTP post,然后运行回调通知;客户端同时挂起。

TDSCallbackWithMethod = class(TDBXCallback)
private
  FCallbackMethod: TDSCallbackMethod;
public
  constructor Create(ACallbackMethod: TDSCallbackMethod);
  function Execute(const Args: TJSONValue): TJSONValue; override; //executes FCallbackMethod
end;

procedure TMainForm.BtnPostOrderHistoryClick(Sender: TObject);
var
  callback: TDBXCallback;
  ServerMethods1Client: TServerMethods1Client;
begin
  //Define Callback to show notification
  callback := TDSCallbackWithMethod.Create(
    function(const Args: TJSONValue): TJSONValue
    var
      errors: integer;
    begin
      errors := Args.GetValue<integer>('errors');

      if errors = 0 then
        showNotification(StrSentSuccessfully)
      else
        showNotification(StrSendingFailed + '(' + errors.ToString + ' not sent)');

      result := TJsonTrue.Create;
    end);

  //Call Server Method
  ServerMethods1Client := TServerMethods1Client.Create(DMServerConnection.SQLConnection1.DBXConnection);
  try
    ServerMethods1Client.postCustOrderHistAsync(EditCustomerId.Text, callback)
  finally
    ServerMethods1Client.Free;
  end;
end;

应该如何设计才能异步调用服务器方法,并在完成后让服务器运行回调? Post 函数应该能够被同一用户多次调用或同时调用多次。 线程应该在服务器端还是客户端?如果有人可以提供帮助,我可以发送一个使用 Northwind 数据库的应用程序演示。

注意:我曾尝试在 TTask 中运行客户端函数调用,它在用户一次运​​行该函数时有效。但是当服务器方法同时运行多次时,我得到一个“DBXError…读取错误…回调期望 X 得到 Y”。似乎在客户端等待第一个请求的响应回调格式时,它与从第二个请求发起的其他 tcp 协议数据包混淆了。我尝试在服务器端运行 ttask,但出现异常“TOLEDBCommand.Destroy - 未释放接口”

【问题讨论】:

    标签: multithreading delphi callback datasnap


    【解决方案1】:

    查看this 示例,它逐步介绍了创建回调的步骤。基本上,您需要一个 TDSClientCallbackChannelManager(组件)及其 RegisterCallback 函数来告诉 datasnap 客户端在从服务器触发回调时在客户端调用什么方法(从 TDBXCallback 继承的对象)。您需要将客户端会话 ID 传递给服务器,以便它可以使用 NotifyCallBack 调用正确的客户端。然后从该回调方法中,您可以在 TThread.Queue 中执行您需要的操作以确保安全。您可能需要在服务器返回的 JSON 中创建某种唯一标识符(或者您的 CustomerID 可能会起作用),以便您的客户端知道哪个调用是哪个调用的结果。

    【讨论】:

    • 感谢 TDC 指出这一点。我研究了重量级回调。虽然我认为这可能有效,但我认为它不会达到这个目的。我有很多客户端将异步调用的服务器函数,并且将每个函数注册到 channelId 是一种矫枉过正。我觉得它适用于服务器想要向客户端发送消息时。但就我而言,这是客户端在不阻塞主线程的情况下期待发布状态结果。如果我错了,请纠正我。
    【解决方案2】:

    为了简化客户端的服务器方法调用,我从客户端移除了Callback,只创建了等待服务器响应的并行线程。我仍然得到同样的错误“DBXError…读取错误…回调期望 X 得到 Y”。所以那时我知道错误不是回调问题,而是线程之间的干扰。事实证明,当我创建客户端的代理方法时,所有线程都使用同一个 DBXConnection 实例。这将使不同服务器调用/响应之间的 SQL 连接丢失并出现解析错误。我做了一个函数“getNewSqlConnection”,它将 TSQLConnection 的所有设置复制到一个新实例中。
    现在客户端调用方法如下所示:

    procedure TMainForm.BtnPostOrderHistoryClick(Sender: TObject);
    begin
      ttask.Run(
        procedure
        var
          ServerMethods1Client: TServerMethods1Client;
          SqlConnectionLocal: TSqlConnection;
          errors: Integer;
        begin
          // Call Server Method
          SqlConnectionLocal := DMServerConnection.getNewSqlConnection(Self);
          ServerMethods1Client := TServerMethods1Client.Create(SqlConnectionLocal.DBXConnection);
          try
            errors := ServerMethods1Client.postCustOrderHist(EditCustomerId.Text);
            if errors = 0 then
              TThread.Synchronize(nil,
                Procedure
                begin
                  showNotification(StrSentSuccessfully)
                end)
            else
              TThread.Synchronize(nil,
                Procedure
                begin
                  showNotification(StrSendingFailed + '(' + errors.ToString + ' not sent)')
                end);
          finally
            ServerMethods1Client.Free;
            SqlConnectionLocal.Free;
          end;
        end);
    end;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-12-19
      • 1970-01-01
      • 2015-11-28
      • 2020-01-17
      相关资源
      最近更新 更多