Обновление пользовательского интерфейса в ожидании DataSnap - PullRequest
4 голосов
/ 02 марта 2012

Я создал приложение MDI Delphi в Delphi XE2, которое подключается к серверу DataSnap через компонент TSQLConnection (driver = datasnap).Щелчок правой кнопкой мыши по TSQLConnection во время разработки позволяет мне сгенерировать клиентские классы DataSnap (ProxyMethods).

Моя цель состоит в том, чтобы на клиентской стороне было время с таймером [0:00], которое показываетсколько времени занимает запрос DataSnap к сервису, обновляется каждую 1 секунду.Два подхода, которые я пробовал, но они не работают:

Метод № 1

Использование TTimer с интервалом в 1 секунду, который обновляет истекшее время, пока выполняется ProxyMethod.Я включаю таймер перед вызовом ProxyMethod.Во время работы ProxyMethod событие OnTimer не срабатывает - точка останова в коде никогда не срабатывает.

Метод № 2

То же, что и метод № 1, за исключением того, что таймер является TJvThreadTimer.Во время работы ProxyMethod происходит событие OnTimer, но код OnTimer не выполняется, пока не завершится ProxyMethod.Это очевидно, потому что точка останова в коде OnEvent быстро срабатывает после завершения ProxyMethod - подобно тому, как события OnTimer были поставлены в очередь в основном потоке VCL.

Более того, если щелкнуть в любом месте клиентского приложения во время работы медленного ProxyMethod, приложение будет зависать (в строке заголовка отображается «Не отвечает»).

Iдумаю, что лучшее решение - это перенести выполнение ProxyMethods в отдельный поток.Однако должно быть существующим решением - поскольку связанная с зависшим проблема приложения, похоже, будет обычной жалобой.Я просто не могу найти решение.

Любые предложения приветствуются.В противном случае я смирюсь с тем, чтобы переместить выполнение ProxyMethod в отдельный поток.

Ответы [ 4 ]

4 голосов
/ 02 марта 2012

Вы определили фундаментальную проблему.Ваш запрос выполняется в потоке пользовательского интерфейса и блокирует этот поток во время его выполнения.Обновления пользовательского интерфейса не могут происходить, сообщения таймера не могут запускаться и т. Д.

Я думаю, что наилучшим решением является перенос выполнения ProxyMethods в отдельный поток.Тем не менее, должно быть существующим решением - поскольку связанная с зависшим проблема приложения выглядит так, как будто это общая жалоба.Я просто не могу найти решение.

Вы уже нашли единственное решение проблемы.Вы должны выполнить свой длительный запрос в потоке, отличном от потока пользовательского интерфейса.

3 голосов
/ 04 октября 2012

Используя приведенную выше идею, я сделал простое решение, которое будет работать для всех классов (автоматически). Я создал 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;

Спасибо, теперь я могу обновить интерфейс с помощью обратного вызова сервера.

3 голосов
/ 03 марта 2012

В случае, если кто-то хочет знать, решение было довольно простым для реализации.Теперь у нас есть истекшие рабочие часы [0:00], которые увеличиваются каждый раз, когда клиентское приложение ожидает, пока сервер DataSnap обработает запрос.По сути, это то, что мы сделали.( Особая благодарность тем, кто делится своими решениями, - которые помогли мне мыслить. )

Сгенерированные сервером классы (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 на двашаги.Первым шагом является сохранение значений параметров в приватных переменных.Это позволяет методу _Execute() иметь все, что ему нужно знать, когда он выполняет фактический метод ProxyMethods, результат которого сохраняется в FResult для последующего извлечения.

Если класс ProxyMethods имеет несколько функций, вы легкооберните каждый метод и установите внутреннюю переменную (например, FProcID) при вызове метода для установки приватных переменных.Таким образом, метод _Execute() может использовать FProcID, чтобы узнать, какой ProxyMethod должен выполняться ...

Вы можете удивиться, почему Thread не освобождает себя.Причина в том, что я не смог устранить ошибку « Ошибка потока: недопустимый дескриптор (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 для обновления пользовательского интерфейса.TTimer не работает даже при выполнении ProxyMethod в отдельном потоке, поскольку поток 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;
0 голосов
/ 05 октября 2012

Как вы заставили компилятор использовать ваш модифицированный Data.DBXCommand.pas

Поместив измененный Data.DBXCommand.pas в папку вашего проекта.

...