繁体   English   中英

在等待DataSnap时更新客户端UI

[英]Updating client UI while waiting for DataSnap

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

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

方法#1

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

方法#2

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

此外,在慢速ProxyMethod运行时单击客户端应用程序上的任何位置会使应用程序显示为挂起(标题栏中显示“Not Responding”)。

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

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

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

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

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

使用上述想法,我做了一个简单的解决方案,适用于所有类(自动)。 我创建了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。

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

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

ProxyMethods.pas

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

ProxyWrapper.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以供以后检索。

如果ProxyMethods类具有多个函数,则在调用方法设置私有变量时,可以轻松地包装每个方法并设置内部变量(例如, FProcID )。 这样_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]递增的关键是使用TJvThreadTimer来更新UI。 即使在单独的线程中执行ProxyMethod, TTimer也不起作用,因为VCL线程正在等待WaitFor ,因此TTimer.OnTimer()WaitFor完成之前不会执行。

从信息TJvTheadTimer.OnTimer()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;

你是如何强制编译器使用你修改过的Data.DBXCommand.pas的?

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM