2012-03-01 84 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放入您的項目文件夾中。