共用方式為


最佳化效能:物件行為

了解 WPF 物件的內建行為可以協助您在功能與效能之間做正確的取捨。

這個主題包含下列章節。

  • 不移除物件的事件處理常式可以保持物件持續運作
  • 相依性屬性和物件
  • Freezable 物件
  • 使用者介面虛擬化
  • 相關主題

不移除物件的事件處理常式可以保持物件持續運作

物件傳給事件的委派其實是該物件的參考。 因此,事件處理常式可能會讓物件持續運作得比預期更久。 對已註冊接聽物件事件的物件執行清除時,最重要的動作是在釋放物件之前移除該委派。 讓不需要的物件持續運作會增加應用程式的記憶體使用量。 特別是在物件為輯樹狀結構或視覺化樹狀結構的根項目時更是如此。

WPF 使用弱式事件接聽程式模式,以在物件存留期間難以判斷來源與接聽程式關係的情況下,追蹤有助於判斷的事件。 部分現有的 WPF 事件會使用這個模式。 如果您以自訂事件實作物件,這個模式對您可能有用。 如需詳細資訊,請參閱弱式事件模式

有數個工具 (例如 CLR 分析工具和工作集檢視器) 可以提供所指定處理程序的記憶體使用量相關資訊。 CLR 分析工具包含一些相當有用的配置分析檢視,其中包括配置類型長條圖、配置和呼叫圖形、時間表 (顯示各代記憶體回收以及回收之後 Managed 堆積 (Heap) 的狀態),以及呼叫樹狀結構 (顯示每個方法的配置和組件負載)。 如需詳細資訊,請參閱 .NET Framework 開發人員中心 (英文)。

相依性屬性和物件

一般來說,存取 DependencyObject 的相依性屬性不會比存取 CLR 屬性慢。 姑且不論設定屬性值會帶來少許效能負荷,取得值與從 CLR 屬性取得值的速度一樣快。 相依性屬性得以省去這少許效能負荷的關鍵在於,其能夠支援強大的功能,例如資料繫結、動畫、繼承和樣式。 如需詳細資訊,請參閱 相依性屬性概觀

DependencyProperty 最佳化

您應謹慎定義應用程式中的相依性屬性。 如果 DependencyProperty 只會影響呈現型別中繼資料 (Metadata) 選項,而不會影響 AffectsMeasure 等其他中繼資料選項,您應該覆寫中繼資料來進行相關標示。 如需覆寫或取得屬性中繼資料的詳細資訊,請參閱相依性屬性中繼資料

如果不是所有屬性變更都會實際影響測量、排列和呈現,手動用屬性變更處理常式使測量、排列和呈現活動失效會更有效率。 例如,您可能決定只有在值大於設定的限制時重新呈現背景。 在此情況下,屬性變更常式只會在值超過設定的限制時使呈現失效。

無法自由將 DependencyProperty 變成可繼承的屬性

根據預設,已註冊的相依性屬性是不可繼承的。 不過,您可以明確讓任何屬性變成可繼承的。 雖然這是很有用的功能,但是將屬性轉換為可繼承的會增加讓屬性失效所需的時間長度,進而影響效能。

小心使用 RegisterClassHandler

呼叫 RegisterClassHandler 可以讓您儲存執行個體狀態,但是請務必注意,由於每個執行個體上都會呼叫這個處理常式,因此可能會造成效能問題。 只有在應用程式需要您儲存執行個體狀態時才使用 RegisterClassHandler

註冊時設定 DependencyProperty 的預設值

建立需要預設值的 DependencyProperty 時,請在預設中繼資料中加入預設值,然後做為參數傳遞至 DependencyPropertyRegister 方法。 請使用這個方式,而不是在建構函式 (Constructor) 中或項目的每個執行個體上設定屬性值。

使用登錄來設定 PropertyMetadata 值

建立 DependencyProperty 時,可以選擇使用 RegisterOverrideMetadata 方法來設定 PropertyMetadata。 雖然您的物件可能已有靜態建構函式可以呼叫 OverrideMetadata,但是這不是最佳的解決方案而且會影響效能。 如需最佳的效能,請在呼叫 Register 時設定 PropertyMetadata

Freezable 物件

Freezable 是一種特殊的物件,它有兩種狀態:未凍結和凍結。 只要一有可能就凍結物件,將能改善應用程式的效能並且減少其工作集。 如需詳細資訊,請參閱 Freezable 物件概觀

每個 Freezable 變更時,都會引發 Changed 事件。 不過,就應用程式效能的角度來說變更告知所費不眥。

請考量下列範例,範例中每個 Rectangle 都使用相同的 Brush 物件:

            rectangle_1.Fill = myBrush
            rectangle_2.Fill = myBrush
            rectangle_3.Fill = myBrush
            ' ...
            rectangle_10.Fill = myBrush
rectangle_1.Fill = myBrush;
rectangle_2.Fill = myBrush;
rectangle_3.Fill = myBrush;
// ...
rectangle_10.Fill = myBrush;

根據預設,WPF 會針對 SolidColorBrush 物件的 Changed 事件提供事件處理常式,以使 Rectangle 物件的 Fill 屬性失效。 在此情況下,每次 SolidColorBrush 需要引發其 Changed 事件時,都需要針對每個 Rectangle 叫用 (Invoke) 回呼函式 (Callback Function)。這些回呼函式叫用累積的越多,效能降得越多。 此外,在這個時候加入或移除處理常式會導致效能耗損,因為應用程式需要周遊整個清單以執行動作。 如果您的應用案例絕對不會變更 SolidColorBrush,您還是得付出維護不需要的 Changed 事件處理常式的成本。

凍結 Freezable 可以改善效能,因為不再需要耗用資源來維護變更告知。 下表顯示一個簡單的 SolidColorBrush 在它的 IsFrozen 屬性是設定為和不是設定為 true 時的大小。 其中假設將一個筆刷套用至十個 Rectangle 物件的 Fill 屬性。

狀態

大小

凍結的 SolidColorBrush

212 個位元組

未凍結的 SolidColorBrush

972 個位元組

下列程式碼範例示範這個概念:

            Dim frozenBrush As Brush = New SolidColorBrush(Colors.Blue)
            frozenBrush.Freeze()
            Dim nonFrozenBrush As Brush = New SolidColorBrush(Colors.Blue)

            For i As Integer = 0 To 9
                ' Create a Rectangle using a non-frozed Brush.
                Dim rectangleNonFrozen As New Rectangle()
                rectangleNonFrozen.Fill = nonFrozenBrush

                ' Create a Rectangle using a frozed Brush.
                Dim rectangleFrozen As New Rectangle()
                rectangleFrozen.Fill = frozenBrush
            Next i
Brush frozenBrush = new SolidColorBrush(Colors.Blue);
frozenBrush.Freeze();
Brush nonFrozenBrush = new SolidColorBrush(Colors.Blue);

for (int i = 0; i < 10; i++)
{
    // Create a Rectangle using a non-frozed Brush.
    Rectangle rectangleNonFrozen = new Rectangle();
    rectangleNonFrozen.Fill = nonFrozenBrush;

    // Create a Rectangle using a frozed Brush.
    Rectangle rectangleFrozen = new Rectangle();
    rectangleFrozen.Fill = frozenBrush;
}

變更未凍結 Freezable 上的處理常式可能會讓物件持續運作

物件傳給 Freezable 物件之 Changed 事件的委派,其實是該物件的參考。 因此,Changed 事件處理常式可以讓物件持續運作得比預期更久。 對已註冊接聽 Freezable 物件之 Changed 事件的物件執行清除時,最重要的動作是在釋放物件之前移除該委派。

WPF 也會攔截內部的 Changed 事件。 例如,所有以 Freezable 做為值的相依性屬性都會自動接聽 Changed 事件。 以 Brush 為值的 Fill 屬性會說明這個概念。

            Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
            Dim myRectangle As New Rectangle()
            myRectangle.Fill = myBrush
Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;

將 myBrush 指派給 myRectangle.Fill 時,SolidColorBrush 物件的 Changed 事件中會加入指回 Rectangle 物件的委派。 這表示下列程式碼不會實際將 myRect 納入記憶體回收作業:

            myRectangle = Nothing
myRectangle = null;

在這個案例中,myBrush 仍然會使 myRectangle 持續運作,並且在這個物件引發 Changed 事件時回頭呼叫這個物件。 請注意,將 myBrush 指派給新 RectangleFill 屬性,不過是將另一個事件處理常式加入至 myBrush。

清除這類物件的建議方式是從 Fill 屬性移除 Brush,這樣就會移除 Changed 事件處理常式。

            myRectangle.Fill = Nothing
            myRectangle = Nothing
myRectangle.Fill = null;
myRectangle = null;

使用者介面虛擬化

WPF 也提供另一種 StackPanel 項目,這種項目會自動將資料繫結子內容「虛擬化」。 在此情況下,「虛擬化」一詞是指一種技術,這種技術可以依據畫面上所顯示的項目,從眾多資料項目當中產生物件的子集。 在指定時間畫面上只會出現少數項目時,產生大量 UI 項目會耗用許多記憶體和處理器資源。 VirtualizingStackPanel 會 (透過 VirtualizingPanel 所提供的功能) 計算可見項目 (Item),並從 ItemsControl (例如 ListBoxListView) 搭配 ItemContainerGenerator 使用,只為可見項目 (Item) 建立項目 (Element)。

做為效能最佳化的方法,這些項目的視覺物件只會在可於螢幕上看見時產生或持續運作。 當不再位於控制項的可見區域中時,視覺物件會遭到移除。 這項功能不能與資料虛擬化混淆,在資料虛擬化中資料物件不存在於本機集合中,而是視需要透過資料流傳入。

下表顯示將 5000 個 TextBlock 項目加入及呈現至 StackPanelVirtualizingStackPanel 的耗用時間。 這個案例中的度量單位,代表從將文字字串附加至 ItemsControl 物件的 ItemsSource 屬性,至面板項目顯示文字字串的時間。

主面板

轉譯時間 (毫秒)

StackPanel

3210

VirtualizingStackPanel

46

請參閱

概念

最佳化 WPF 應用程式效能

應用程式效能規劃

最佳化效能:運用硬體

最佳化效能:配置與設計

最佳化效能:2D 圖形和影像處理

最佳化效能:應用程式資源

最佳化效能:文字

最佳化效能:資料繫結

最佳化效能:其他建議