[英]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.