我在Delphi XE2中创建了一个MDI Delphi应用程序,它通过TSQLConnection
组件(driver = datasnap
)连接到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运行时单击客户端应用程序上的任意位置会使应用程序显示为挂起(标题栏中显示“Not Responding”)。
我认为最好的解决方案是将ProxyMethods的执行移动到一个单独的线程。但是,必须是现有解决方案 - 因为相关的挂起应用程序问题似乎是常见的投诉。我找不到解决办法。
任何建议都表示赞赏。否则,我将辞职,将ProxyMethod执行移动到一个单独的线程中。
答案 0 :(得分:4)
您已确定了根本问题。您的查询正在UI线程中运行,并在运行时阻止该线程。不会发生UI更新,计时器消息无法触发等。
我认为最好的解决方案是将ProxyMethods的执行移动到一个单独的线程。但是,必须是现有解决方案 - 因为相关的挂起应用程序问题似乎是常见的投诉。我找不到解决办法。
您已找到问题的唯一解决方案。您必须在UI线程以外的线程中运行长时间运行的查询。
答案 1 :(得分:3)
如果有人想知道,解决方案实施起来相当简单。我们现在有一个工作的经过时间[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()
代码如下所示,它会更新应用程序的状态栏:
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;
答案 2 :(得分: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。
答案 3 :(得分:0)
您是如何强制编译器使用您修改的Data.DBCommand.pass>的
将修改后的Data.DBCommand.pas放入项目文件夹中。