簡體   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