4

TSQLConnectionコンポーネント ( )を介して DataSnap サーバーに接続する Delphi XE2 で MDI Delphi アプリを作成しましたdriver = datasnap。設計時に を右クリックTSQLConnectionすると、DataSnap クライアント クラス (ProxyMethods) を生成できます。

私の目標は、クライアント側に、DataSnap 要求が処理されるまでの時間を示す経過時間クロック [0:00] を設定し、1 秒ごとに更新することです。私が試したがうまくいかない2つのアプローチは次のとおりです。

方法 1

TTimerProxyMethod の実行中に経過時間クロックを更新する 1 秒間隔で を使用します。ProxyMethod を呼び出す直前にタイマーを有効にします。ProxyMethod の実行中は、OnTimerイベントは発生しません。コード内のブレークポイントに到達することはありません。

方法 2

タイマーが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

誰かが知りたい場合に備えて、ソリューションは実装がかなり簡単でした。これで、クライアント アプリが DataSnap サーバーが要求を処理するのを待っている間はいつでも増加する、動作中の経過時間クロック [0:00] ができました。本質的に、これが私たちがしたことです。(解決策を共有してくれた人たちに特に感謝します。私の考えを導くのに役立ちました。 )

サーバーによって生成されたクラス (ProxyMethods) は、VCL スレッドで作成する必要がありますが、別のスレッドで実行する必要があります。これを行うために、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 の実行を 2 つのステップに分割したことがわかります。最初のステップは、パラメーターの値をプライベート変数に格納することです。これにより、_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;

WaitForProxyMethods スレッドが完了するまでコードの実行を中断します。smw.GetResultスレッドの実行が完了するまで必要な値を返さないため、これが必要です。プロキシ実行スレッドがビジー状態のときに経過時間クロック [0:00] をインクリメントするための鍵は、 を使用しTJvThreadTimerて UI を更新することです。ProxyMethodTTimerが別のスレッドで実行されていても、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 に答える