4

TSQLConnection我在 Delphi XE2 中创建了一个 MDI Delphi 应用程序,它通过组件 ( driver = datasnap)连接到 DataSnap 服务器。在设计时右键单击TSQLConnection可让我生成 DataSnap 客户端类 (ProxyMethods)。

我的目标是在客户端有一个经过的时钟 [0:00],它显示 DataSnap 请求需要多长时间才能提供服务,每 1 秒更新一次。我尝试过但不起作用的两种方法是:

方法#1

TTimer在执行 ProxyMethod 时使用1 秒间隔更新经过的时间时钟。我在调用 ProxyMethod 之前启用了计时器。当 ProxyMethod 运行时,OnTimer事件不会触发——代码中的断点永远不会被命中。

方法#2

与方法 #1 相同,除了计时器是TJvThreadTimer. 当 ProxyMethod 运行时,该OnTimer事件会触发,但OnTimer直到 ProxyMethod 完成后代码才会执行​​。这很明显,因为OnEvent在 ProxyMethod 完成后,代码中的断点会迅速连续命中——就像OnTimer事件都已在主 VCL 线程中排队一样。

此外,在运行缓慢的 ProxyMethod 时单击客户端应用程序上的任意位置会使应用程序看起来被挂起(标题栏中出现“未响应”)。

我认为最好的解决方案是将 ProxyMethods 的执行移至单独的线程。但是,必须有一个现有的解决方案——因为相关的挂起应用程序问题似乎是一个常见的投诉。我只是找不到解决方案。

任何建议表示赞赏。否则,我将辞职,将 ProxyMethod 执行移到单独的线程中。

4

4 回答 4

4

你已经确定了根本问题。您的查询在 UI 线程中运行,并在运行时阻止该线程。不能发生 UI 更新,不能触发计时器消息等。

我认为最好的解决方案是将 ProxyMethods 的执行移至单独的线程。但是,必须有一个现有的解决方案——因为相关的挂起应用程序问题似乎是一个常见的投诉。我只是找不到解决方案。

您已经找到了解决问题的唯一方法。您必须在 UI 线程以外的线程中运行长时间运行的查询。

于 2012-03-01T20:45:10.523 回答
3

如果有人想知道,该解决方案实施起来相当简单。我们现在有一个工作已用时间时钟 [0:00],只要客户端应用程序等待 DataSnap 服务器为请求提供服务,它就会递增。本质上,这就是我们所做的。(特别感谢那些分享他们的解决方案的人——这有助于指导我的思考。

服务器生成的类(ProxyMethods)必须在 VCL 线程中创建,但在单独的线程中执行。为此,我们创建了一个 ProxyMethods 包装类和一个 ProxyMehtods 线程类(所有这些都是为本示例设计的,但仍说明了流程):

代理方法.pas

...
type
  TServerMethodsClient = class(TDSAdminClient)
  private
    FGetDataCommand: TDBXCommand;
  public
    ...
    function GetData(Param1: string; Param2: string): string;
    ...
  end;

代理包装器.pas

...
type
  TServerMethodsWrapper = class(TServerMethodsClient)
  private
    FParam1: string;
    FParam2: string;
    FResult: string;
  public
    constructor Create; reintroduce;
    procedure GetData(Param1: string; Param2: string);
    procedure _Execute;
    function GetResult: string;
  end;

  TServerMethodsThread = class(TThread)
  private
    FServerMethodsWrapper: TServerMethodsWrapper;
  protected
    procedure Execute; override;
  public
    constructor Create(ServerMethodsWrapper: TServerMethodsWrapper);
  end;

implementation

constructor TServerMethodsWrapper.Create;
begin
  inherited Create(ASQLServerConnection.DBXConnection, True);
end;

procedure TServerMethodsWrapper.GetData(Param1: string; Param2: string);
begin
  FParam1 := Param1;
  FParam2 := Param2;
end;

procedure TServerMethodsWrapper._Execute;
begin
  FResult := inherited GetData(FParam1, FParam2);
end;

function TServerMethodsWrapper.GetResult: string;
begin
  Result := FResult;
end;

constructor TServerMethodsThread.Create(ServerMethodsWrapper: TServerMethodsWrapper);
begin
  FServerMethodsWrapper := ServerMethodsWrapper;
  FreeOnTerminate := False;
  inherited Create(False);
end;

procedure TServerMethodsThread.Execute;
begin
  FServerMethodsWrapper._Execute;
end;

可以看到我们将 ProxyMethod 的执行分为两步。第一步是将参数的值存储在私有变量中。这允许该_Execute()方法在执行实际的 ProxyMethods 方法时拥有它需要知道的一切,其结果存储在其中以FResult供以后检索。

FProcID如果 ProxyMethods 类具有多个函数,您可以轻松地包装每个方法并在调用该方法以设置私有变量时设置一个内部变量(例如)。这样,该_Execute()方法可以FProcID用来知道要执行哪个 ProxyMethod...

您可能想知道为什么线程不释放自己。原因是当线程进行自己的清理时,我无法消除错误“线程错误:句柄无效(6) ”。

调用包装类的代码如下所示:

var
  smw: TServerMethodsWrapper;
  val: string;
begin
  ...
  smw := TServerMethodsWrapper.Create;
  try
    smw.GetData('value1', 'value2');
    // start timer here
    with TServerMethodsThread.Create(smw) do
    begin
      WaitFor;
      Free;
    end;
    // stop / reset timer here
    val := smw.GetResult;
  finally
    FreeAndNil(smw);
  end;
  ...
end;

暂停代码执行,WaitFor直到 ProxyMethods 线程完成。这是必要的,因为smw.GetResult在线程完成执行之前不会返回所需的值。在代理执行线程繁忙时使经过时间时钟 [0:00] 递增的关键是使用 aTJvThreadTimer来更新 UI。即使 ProxyMethod 在单独的线程中执行, ATTimer也不起作用,因为 VCL 线程正在等待WaitFor,因此在TTimer.OnTimer()完成之前不会执行WaitFor

从信息上看,TJvTheadTimer.OnTimer()代码如下所示,它更新了应用程序的状态栏:

var
  sec: Integer;
begin
  sec := DateUtils.SecondsBetween(Now, FBusyStart);
  StatusBar1.Panels[0].Text := Format('%d:%.2d', [sec div 60, sec mod 60]);
  StatusBar1.Repaint;
end;
于 2012-03-02T22:04:47.947 回答
3

使用上述想法,我制作了一个适用于所有类的简单解决方案(自动)。我创建了 TThreadCommand 和 TCommandThread 如下:

   TThreadCommand = class(TDBXMorphicCommand)
    public
      procedure ExecuteUpdate; override;
      procedure ExecuteUpdateAsync;
    end;

    TCommandThread = class(TThread)
       FCommand: TDBXCommand;
    protected
      procedure Execute; override;
    public
      constructor Create(cmd: TDBXCommand);
    end;



    { TThreadCommand }

    procedure TThreadCommand.ExecuteUpdate;
    begin
      with TCommandThread.Create( Self ) do
      try
        WaitFor;
      finally
        Free;
      end;
    end;

    procedure TThreadCommand.ExecuteUpdateAsync;
    begin
      inherited ExecuteUpdate;
    end;

    { TCommandThread }

    constructor TCommandThread.Create(cmd: TDBXCommand);
    begin
      inherited Create(True);
      FreeOnTerminate := False;
      FCommand := cmd;
      Resume;
    end;

    procedure TCommandThread.Execute;
    begin
      TThreadCommand(FCommand).ExecuteUpdateAsync;
    end;

然后改Data.DBXCommon.pas:

function TDBXConnection.DerivedCreateCommand: TDBXCommand; 
begin    
   //Result:= TDBXMorphicCommand.Create (FDBXContext, Self);    
   Result:= TThreadCommand.Create (FDBXContext, Self); 
end;

多亏了这一点,现在我可以使用服务器回调更新 UI。

于 2012-10-04T13:58:04.433 回答
0

您如何强制编译器使用您修改后的 Data.DBXCommand.pas?

通过将修改后的 Data.DBXCommand.pas 放入您的项目文件夹中。

于 2012-10-05T08:18:12.343 回答