2012-03-01 2 views
4

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

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

Способ № 1

Use a TTimer with a 1 second interval that updates the elapsed time clock while a ProxyMethod is being execute. I enable the timer just before calling the ProxyMethod. While the ProxyMethod is running, the OnTimer event doesn't fire -- a breakpoint in the code is never hit.

Способ № 2

Same as Method #1, except the timer is a TJvThreadTimer. While the ProxyMethod is running, the OnTimer event fires, but the OnTimer code doesn't get execute until after the ProxyMethod completes. This is evident because a breakpoint in the OnEvent code gets hit in rapid succession after the ProxyMethod completes -- like the OnTimer events have all been queued in the main VCL thread.

Кроме того, нажав кнопку в любом месте клиентского приложения, когда работает медленный ProxyMethod, приложение выглядит зависающим («Not Res» ponding "появляется в заголовке).

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

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

ответ

4

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

I think the best solution is to move the execution of the ProxyMethods to a separate thread. However, there must be an existing solution -- because the related hung app issue seems like it would be a common complaint. I just can't find the solution.

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

+1

Вот что я подумал. Вы бы подумали, что с тем, как долго DataSnap был вокруг, что было бы что-то встроенное для обработки состояния занятости пользовательского интерфейса или что-то еще ... DataSnap также не имеет обратного вызова, чтобы определить пользовательский интерфейс, сколько данных было передано, поэтому пользовательский интерфейс может внедрить индикатор прогресса для больших результатов поиска/толчков. –

+0

(Оставляя это обозначенным как «правильный ответ», потому что решение может принимать любую форму - тот, который приведен ниже, просто возможен. Слишком плохо, вы не можете отметить больше, чем «один» ответ как «правильный».) –

3

В случае, если кто-то хочет знать, решение было довольно простым в реализации. Теперь у нас есть рабочие истекшие временные часы [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, чтобы узнать, какой прокси-метод выполнить ...

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

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

+1

вы вынуждаете компилятор использовать измененные Data.DBXCommand.pas? Каждый раз, когда я пытался изменить один из файлов DBX Framework, он всегда обходит мои изменения - даже если я поместил измененный файл непосредственно в папку проекта. –

+0

Помещая измененную Data.DBXCommand.pas в папку проекта. –

0

How did you force the compiler to use your modified Data.DBXCommand.pas?

Поместив модифицированный Data.DBXCommand.pas в папку проекта.