2012-03-01 49 views
4

我创建在Delphi XE2一个MDI Delphi应用程序,其经由TSQLConnection组分(driver = datasnap)连接到DataSnap服务器。在设计阶段右键单击TSQLConnection,可以生成DataSnap客户端类(ProxyMethods)。更新客户端UI在等待的DataSnap

我的目标是具有经过时间时钟[0时]在客户端侧,显示了的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运行,使应用程序似乎挂起(“不Res积水“出现在标题栏中)。

我认为最好的解决方案是将ProxyMethods的执行移至单独的线程。但是,必须是一个现有的解决方案 - 因为相关的挂起应用程序问题似乎将是一个常见投诉。我找不到解决方案。

任何建议表示赞赏。否则,我会自to将ProxyMethod执行转移到单独的线程中。

回答

4

您已经确定了基本问题。您的查询在UI线程中运行,并在运行时阻塞该线程。没有UI更新可能发生,计时器消息不能启动等。

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.

您已经找到了解决此问题的唯一解决方案。您必须在UI线程以外的线程中运行长时间运行的查询。

+1

这就是我的想法。你会认为DataSnap已经存在了多长时间,会有内置的东西来处理UI繁忙状态或者什么...... DataSnap也缺少一个回调来提示UI有多少数据已经被传输,所以UI可以为大数据检索/推送实施进度指示器。 – 2012-03-01 21:01:47

+0

(将此标记为'正确答案',因为解决方案可以采用任何形式 - 下面的答案只是可能性而已。太糟糕了,您不能将多于'一个'的答案标记为'正确'。) – 2012-03-05 18:23:47

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来知道要执行哪个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; 
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的更新服务器回调。

+1

你强制编译器使用你修改过的Data.DBXCommand.pas?每次尝试修改其中一个DBX Framework文件时,它总是绕过我的更改 - 即使我将修改的文件直接放在项目文件夹中。 – 2012-10-04 14:52:10

+0

通过将修改后的Data.DBXCommand.pas放入您的项目文件夹中。 – 2012-10-05 08:20:04

0

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

通过将修改后的Data.DBXCommand.pas放入您的项目文件夹中。