Обновление пользовательского интерфейса в ожидании DataSnap
Я создал приложение 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 приводит к зависанию приложения (в строке заголовка отображается "Не отвечает").
Я думаю, что лучшее решение - это перенести выполнение ProxyMethods в отдельный поток. Тем не менее, должно быть существующее решение - потому что проблема с зависшим приложением кажется обычной жалобой. Я просто не могу найти решение.
Любые предложения приветствуются. В противном случае я смирюсь с тем, чтобы переместить выполнение ProxyMethod в отдельный поток.
4 ответа
Вы определили фундаментальную проблему. Ваш запрос выполняется в потоке пользовательского интерфейса и блокирует этот поток во время его выполнения. Обновления пользовательского интерфейса не могут происходить, сообщения таймера не могут срабатывать и т. Д.
Я думаю, что лучшее решение - это перенести выполнение ProxyMethods в отдельный поток. Тем не менее, должно быть существующее решение - потому что проблема с зависшим приложением кажется обычной жалобой. Я просто не могу найти решение.
Вы уже нашли единственное решение проблемы. Вы должны выполнить свой длительный запрос в потоке, отличном от потока пользовательского интерфейса.
Используя приведенную выше идею, я сделал простое решение, которое будет работать для всех классов (автоматически). Я создал 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;
Благодаря этому, теперь я могу сделать обновление интерфейса с обратным вызовом сервера.
В случае, если кто-то хочет знать, решение было довольно просто реализовать. Теперь у нас есть истекшие рабочие часы [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 выполнить...
Вы можете удивиться, почему Нить не освобождает себя. Причина в том, что я не смог устранить ошибку " Ошибка потока : недопустимый дескриптор (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;
Как вы заставили компилятор использовать ваш измененный Data.DBXCommand.pas?
Поместив измененный Data.DBXCommand.pas в папку вашего проекта.