2015-02-07 74 views
-4

我的Delphi應用程序有兩個活動(看起來)必須都出現在UI線程中。在大多數方面,它是一個單線程應用程序。我在這裏有一個問題,因爲我已經使用了Application.ProcessMessages,並且我希望將它最小化,因爲它可能會導致問題。不知道如何解決這個衝突

我將首先描述成分,然後是問題。

應用程序可以打開多個「文檔」表單,但爲了簡單起見,我只能引用一個文檔表單。每種表格都可以與外部設備通信。

有些情況下,可能會出現的情況,其中程序被建立循環,直到設備的某種情況下允許我們退出循環用戶取消操作。設備接口沒有線程,必須輪詢。

這裏的問題:

當用戶想要關閉文檔,我們可以在這些循環中的一個。這是一個問題,因爲所有這些都在UI線程中......我沒有一個很好的方法來打破文檔表單中的循環,所以我可以關閉文檔。 (DeviceReady或CancelKeyPressed) Application.ProcessMessages;

原液:

我原來的解決方案是做到以下幾點,當用戶試圖關閉該文檔:

  1. 發佈用戶的Windows消息文件的形式告訴它我們想關。
  2. 文檔表單接收到消息並中斷任何活動循環並將消息發送回主窗體,表示我們已準備好關閉。
  3. 主窗體循環,處理消息和輪詢檢查以查看文檔是否準備關閉。
  4. 當文檔準備關閉時,它會完成文檔的關閉。

這在大部分時間都適用,但它很複雜 - 尤其是因爲我們一次打開多個文檔。

其他可能的解決方案

  • ,因爲它似乎對我來說,所有這一切都必須出現在主線程,它移動到一個線程不會聽起來像一個解決方案。

  • 曾想過讓文檔異步關閉,但我們需要知道文檔關閉何時完成,因此我們可以讓用戶執行其他操作,如打開另一個文檔。

  • 這些循環相當快; 1)發送「停止」消息2)等待___ ms並處理消息以使其工作3)關閉它。我認爲這將是一個質量較差的設計,可能會很弱。

  • 將此循環轉換爲狀態機。(傳統)設備接口不是線程化的,必須進行輪詢。我需要創建一個線程來執行輪詢並將消息發佈到表單以引發事件。然後,如果發生這樣的事件或按下一個鍵,將發生將導致下一個狀態發生的事件。這樣,如果表格被關閉,它永遠不會成爲問題。遺憾的是,還需要付出努力將周圍的代碼轉換爲參與狀態機。 (這不是一件壞事。)

結論

Application.ProcessMessages似乎是一個藥物...開始使用它&很快你需要更多的使用它!

任何人有任何建議如何更好地處理這種問題?

問題的答案:

(我張貼了這個問題得到一些意見上的方式,我應該與此去,我一個人工作的開發人員,這是一些地方這是有道理的詢問社區對於一些賢者的建議...)

感謝您的意見!

這些「文檔」是如何管理/引用的?

該文件格式是標準的delphi TForm後裔擁有的一個對象列表。

多線程的規則是,如果你需要一個可能導致主線程不響應的連續循環,它應該是一個線程的形式。你爲什麼不允許把它放在別處?爲什麼它必須在主線程中?

問題是整個應用程序需要等待,直到操作完成或用戶取消爲止。我想我可以將所有傳入的密鑰傳遞給線程。當線程看到循環退出條件時,它可以建議UI線程。

我喜歡這種狀態機方法。

所以我...它有一定的優雅和簡單。它也可以簡化一些其他相關係統。

爲什麼不能通過線程與「設備」對話?我會打破用戶界面的這一部分。

可能這就是我應該做的。我需要的唯一調用是線程安全的。

有很多時候人們可能會告訴你,爲了提高性能而將事情轉移到線程中是一種常見的錯誤。但這是你爲什麼應該的一個主要例子。我無法想象任何嚴格要求主UI線程的接口。你是否使用組件放入表單設計器?如果這是你的挫折,你有沒有嘗試過在不同的線程中動態創建這些組件?

組件包括一個功能區,是的,都是從UI線程創建的。假設我無法在另一個線程中安全地創建組件,並將它們放在用戶將與之交互的窗體上。當然,這實際上不會正常工作?

我有足夠的線程經驗,我可以做到這一點,但希望更確定沒有什麼明顯的我失蹤。

+0

由於我看不到您的代碼,因此很難理解。你目前如何執行這個「循環」?這些「文件」是如何管理/引用的?多線程的規則是,如果你需要一個可能導致主線程不響應的連續循環,它應該是一個線程的形式。你爲什麼不允許把它放在別處?爲什麼它必須在主線程中? – 2015-02-07 00:37:03

+0

我喜歡狀態機方法。 – 2015-02-07 01:35:41

+0

爲什麼不能通過線程與「設備」對話?我會打破用戶界面的這一部分。 – Graymatter 2015-02-07 01:52:13

回答

0

的想法很簡單:

在那裏

unit BaseDocument; 

interface 

uses 
    System.Classes, 
    System.SysUtils, 
    System.Threading; 

type 
    TBaseDocument = class 
    private 
    FIsReady: Boolean; 
    FIsReadyChanged: TNotifyEvent; 
    procedure SetIsReady(const Value: Boolean); 
    procedure SetIsReadyChanged(const Value: TNotifyEvent); 
    protected 
    procedure PerformBackgroundAction(AProc: TProc); 
    public 
    property IsReady: Boolean read FIsReady; 
    property IsReadyChanged: TNotifyEvent read FIsReadyChanged write SetIsReadyChanged; 
    end; 

implementation 

{ TBaseDocument } 

procedure TBaseDocument.PerformBackgroundAction(AProc: TProc); 
begin 
    SetIsReady(False); 
    TTask.Run(nil, 
     procedure 
    begin 
     try 
     AProc(); 
     finally 
     TThread.Synchronize(nil, 
      procedure 
      begin 
      SetIsReady(True); 
      end); 
     end; 
    end); 
end; 

procedure TBaseDocument.SetIsReady(const Value: Boolean); 
begin 
    if FIsReady <> Value 
    then 
    begin 
     FIsReady := Value; 
     if Assigned(FIsReadyChanged) 
     then 
     FIsReadyChanged(Self); 
    end; 
end; 

procedure TBaseDocument.SetIsReadyChanged(const Value: TNotifyEvent); 
begin 
    FIsReadyChanged := Value; 
end; 

end. 

建立的文檔的基類,並把基本的功能(進程在後臺的一些動作)正如你可以看到,我們有IsReady作爲UI部分的指示符,如果文檔已準備好或沒有準備好,以及當IsReady正在改變其狀態時發生事件。

現在我們需要一個基礎文檔來處理這個問題。這也很容易,因爲我們只想讓用戶遠離點擊控件。我們只是把一些東西(這裏的ActivityLayout : TLayoutTLayout.Hittest設置爲True)在所有的表單內容。

ActivityLayout包含一個黑色矩形,其中不透明度設置爲30%以使表單內容變暗,並向用戶提供一些信息,表明仍有一些操作正在進行。

一旦文檔更改了IsReady狀態,表單將被通知並顯示或隱藏ActivityLayout

unit Form.BaseDocument; 

interface 

uses 
    BaseDocument, 

    System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, 
    FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, 
    FMX.Objects, FMX.Layouts; 

type 
    TBaseDocumentForm = class(TForm) 
    ActivityLayout: TLayout; { container for ActivityIndicators } 
    ActivityCurtain: TRectangle; { darken the form content } 
    ActivityIndicator: TAniIndicator; { shows an animation while activity is in progress } 
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
    private 
    FDocument: TBaseDocument; 
    function CanFormClose: Boolean; 
    protected 
    procedure SetDocument(ADocument: TBaseDocument); 
    procedure DoShowActivity(const AVisible: Boolean); 
    procedure DocumentIsReadyChanged(Sender: TObject); 
    procedure DoReloadDocument; virtual; 
    public 

    end; 

var 
    BaseDocumentForm: TBaseDocumentForm; 

implementation 

{$R *.fmx} 
{ TBaseDocumentForm } 

function TBaseDocumentForm.CanFormClose: Boolean; 
begin 
    Result := not Assigned(FDocument) or Assigned(FDocument) and FDocument.IsReady; 
end; 

procedure TBaseDocumentForm.DocumentIsReadyChanged(Sender: TObject); 
begin 
    DoShowActivity(not FDocument.IsReady); 
    if FDocument.IsReady 
    then 
    DoReloadDocument; 
end; 

procedure TBaseDocumentForm.DoReloadDocument; 
begin 
    // override this to load the document after being ready 
end; 

procedure TBaseDocumentForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); 
begin 
    CanClose := CanFormClose; 
end; 

procedure TBaseDocumentForm.SetDocument(ADocument: TBaseDocument); 
begin 
    if FDocument <> ADocument 
    then 
    begin 
     if Assigned(FDocument) and (FDocument.IsReadyChanged = DocumentIsReadyChanged) 
     then 
     FDocument.IsReadyChanged := nil; 

     FDocument := ADocument; 

     if Assigned(FDocument) 
     then 
     begin 
      FDocument.IsReadyChanged := DocumentIsReadyChanged; 
      DocumentIsReadyChanged(FDocument); 
     end 
     else 
     DoShowActivity(False); 
    end; 
end; 

procedure TBaseDocumentForm.DoShowActivity(const AVisible: Boolean); 
begin 
    ActivityLayout.Visible := AVisible; 
    if AVisible 
    then 
    begin 
     { just to ensure the right order } 
     ActivityLayout.BringToFront; 
     ActivityCurtain.BringToFront; 
     ActivityIndicator.BringToFront; 
    end; 
end; 

end. 

這可以很容易地進行擴展,可中斷的後臺操作工作過,並取消對ActivityLayout按鈕。


PS

線程安全時才需要,如果有多個線程執行,訪問相同的在同一時間。如果你可以確保它只能在給定的時間跨度內被一個線程訪問,那麼就不需要線程安全。

+0

感謝您提供有趣的解決方案;它當然擴大了我對這個問題的思考方式。我的應用程序是VCL應用程序,但這並不會改變您的建議的實用性。 – 2015-02-10 16:49:10

-1

你說你的循環速度很快,但看起來速度不夠快。現在我假設你會盡快讓他們更快,所以我不會提出這個建議。

但是你可能已經忘記了這樣一個事實,即使用Break命令可以在循環中斷循環。因此,也許你應該檢查循環週期中是否有可用的地方,你可以檢查CancelKeyPressed的值,然後調用Break命令,以此過早地結束你的循環。現在

while not (DeviceReady or CancelKeyPressed) do 
begin 
    //Do some work 
    ... 
    //Check if premature exit condition is set 
    if CancelKeyPressed then 
    begin 
    //Do some clean up if needed 
    ... 
    Break; 
    end; 
    //Do some more work 
    ... 
    //Check again if premature exit condition is set 
    if CancelKeyPressed then 
    begin 
    //Do some clean up if needed 
    ... 
    Break; 
    end; 
    Application.ProcessMessages; 
end; 

你應該使用break命令時,尤其是如果你是動態創建和釋放的循環中的某些對象要特別atention。爲什麼?

Becouse將break命令放在錯誤的位置可能會阻止某些對象被正確釋放,從而導致內存泄漏。

在這種情況下,您需要確保在釋放所有這些對象之後調用Break命令之前。因此,要麼確保在已經存在的代碼釋放之後放置中斷,或者添加僅在設置了循環過早退出條件時纔會釋放的aditional代碼。

說到控制循環,您可能需要檢查Continue命令,以便讓您打破當前循環週期並繼續下一步。

現在這個命令並不常用,因爲你可以通過簡單地將你的循環代碼的各個塊放入if stamtents中來達到類似的效果。因此,如果符合某些條件,則執行代碼,否則不會,並且您可能已經處於循環結束時。

任何我推薦你檢查過早結束你循環的能力的主要原因是,即使你實現了多線程,你仍然需要等待循環結束。特別是如果在每個循環週期結束時,您使用Syncronize命令幫助更新一些UI控件。

你不想破壞表單,然後讓你的其他線程去嘗試和更新現在不使用的UI組件嗎?我不認爲這會導致一堆訪問衝突。

所以我推薦的是,首先檢查是否有辦法過早地結束你的循環。然後我還建議您嘗試將該代碼移入單獨的線程。

而這樣做的主要原因是您常用的Application.ProcessMessages可能是降低循環性能的主要原因,因爲循環將不會繼續直到Application.ProcessMessages完成其工作。

+0

謝謝;我其實對Break關鍵字非常熟悉,並且經常使用它。我知道使用Application.ProcessMessages會帶來性能損失。當產品最初建成時,這是完成工作的解決方案。您的解決方案無法滿足我的需求。 – 2015-02-10 16:07:59

+0

我從來沒有預料到過這個問題。因爲我不認爲將循環代碼移動到單獨的線程將除非Application.ProcessMessages調用,您可以通過適當的多線程實現來避免是主要原因,但是即使使用多線程,您可能仍然需要等待循環週期結束,然後再銷燬表單。如果這需要很長時間,你仍然會面臨類似的問題。因爲我寫我的建議是嘗試使用多線程組合。 – SilverWarior 2015-02-10 20:34:31