Delphi - 异步 Datasnap 方法调用
Delphi - Asynchronous Datasnap Method Calls
我正在编写一个 Datasnap 应用程序,在 client/server 之间使用 TCP 连接,服务器连接到 SQL 服务器。
Server 有一个包含所有数据集查询和 SQL 连接的数据模块 DM1。 DM1 也有 REST request/client/response 组件。
DM1 有一个公开的函数 PostDataAsync,带有 ID 参数:从数据集生成 json,然后将其通过 HTTP post 发送到 RESTFul 服务。 returns 回调参数中 post 的记录数失败。
这个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 有一个调用带有 ID 参数的服务器方法 PostDataAsync 的按钮,还有一个回调函数“ShowNotification”(使用 windows通知中心显示 Post 通知状态)。
目前,应用程序工作如下:客户端同步调用服务器函数,这意味着主线程等待服务器函数完成 HTTP post 然后 运行s回调通知;客户端同时挂起。
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 协议数据包混淆。我在服务器端尝试了 运行ning ttask,但出现异常 "TOLEDBCommand.Destroy - interfaces not released"
查看 this 示例,它介绍了创建回调的步骤。基本上,您需要一个 TDSClientCallbackChannelManager(组件)及其 RegisterCallback 函数来告诉 datasnap 客户端当从服务器触发回调时在客户端调用什么方法(从 TDBXCallback 继承的对象)。您需要将客户端会话 ID 传递给服务器,以便它可以使用 NotifyCallBack 调用正确的客户端。然后从那个回调方法你可以做你需要做的,在 TThread.Queue 为了安全。您可能需要在 JSON 服务器 returns 中创建某种唯一标识符(或者您的 CustomerID 可能会起作用),以便您的客户知道哪个调用是哪个调用的结果。
为了简化客户端服务器方法调用,我从客户端删除了回调,只创建了等待服务器响应的并行线程。我仍然遇到相同的错误“DBXError...读取错误...回调期望 X 得到 Y”。所以那时我才知道错误不是回调问题,而是线程之间的干扰。事实证明,当我创建客户端的代理方法时,所有线程都使用相同的 DBXConnection 实例。这将使 SQLconnection 在不同服务器 calls/responses 之间丢失并出现解析错误。我做了一个函数 "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;
我正在编写一个 Datasnap 应用程序,在 client/server 之间使用 TCP 连接,服务器连接到 SQL 服务器。
Server 有一个包含所有数据集查询和 SQL 连接的数据模块 DM1。 DM1 也有 REST request/client/response 组件。
DM1 有一个公开的函数 PostDataAsync,带有 ID 参数:从数据集生成 json,然后将其通过 HTTP post 发送到 RESTFul 服务。 returns 回调参数中 post 的记录数失败。
这个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 有一个调用带有 ID 参数的服务器方法 PostDataAsync 的按钮,还有一个回调函数“ShowNotification”(使用 windows通知中心显示 Post 通知状态)。
目前,应用程序工作如下:客户端同步调用服务器函数,这意味着主线程等待服务器函数完成 HTTP post 然后 运行s回调通知;客户端同时挂起。
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 协议数据包混淆。我在服务器端尝试了 运行ning ttask,但出现异常 "TOLEDBCommand.Destroy - interfaces not released"
查看 this 示例,它介绍了创建回调的步骤。基本上,您需要一个 TDSClientCallbackChannelManager(组件)及其 RegisterCallback 函数来告诉 datasnap 客户端当从服务器触发回调时在客户端调用什么方法(从 TDBXCallback 继承的对象)。您需要将客户端会话 ID 传递给服务器,以便它可以使用 NotifyCallBack 调用正确的客户端。然后从那个回调方法你可以做你需要做的,在 TThread.Queue 为了安全。您可能需要在 JSON 服务器 returns 中创建某种唯一标识符(或者您的 CustomerID 可能会起作用),以便您的客户知道哪个调用是哪个调用的结果。
为了简化客户端服务器方法调用,我从客户端删除了回调,只创建了等待服务器响应的并行线程。我仍然遇到相同的错误“DBXError...读取错误...回调期望 X 得到 Y”。所以那时我才知道错误不是回调问题,而是线程之间的干扰。事实证明,当我创建客户端的代理方法时,所有线程都使用相同的 DBXConnection 实例。这将使 SQLconnection 在不同服务器 calls/responses 之间丢失并出现解析错误。我做了一个函数 "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;