最佳化效能:物件行為
了解 WPF 物件的內建行為有助您做出功能和效能之間的正確取捨。
不移除物件的事件處理常式可能會保持物件運作
物件傳遞給其事件的委派實際上是對該物件的參考。 因此,事件處理常式可以讓物件保持運作時間超出預期。 在對已註冊要接聽物件事件的物件執行清除時,務必要在釋放物件之前先移除該委派。 讓不需要的物件保持運行狀態會增加應用程式的記憶體使用量。 特別是當物件是邏輯樹狀結構或視覺化樹狀結構的根目錄時。
WPF 針對適用於來源與接聽程式之間物件存留期關係難以追蹤的情況的事件,引進了弱式事件接聽程式模式。 某些現有的 WPF 事件會使用此模式。 如果您正在以自訂事件實作物件,這個模式可能對您有用。 如需詳細資訊,請參閱弱式事件模式。
有數個工具,例如 CLR 分析工具和工作集檢視器,可以提供有關指定處理序的記憶體使用量資訊。 CLR 分析工具包含許多配置設定檔的極有用檢視,包括已配置類型長條圖、配置和呼叫圖形、顯示各種層代記憶體回收的時間線和回收之後產生的 managed 堆積狀態,以及顯示每個方法配置和組件載入的呼叫樹狀結構。 如需詳細資訊,請參閱效能。
相依性屬性與物件
一般情況下,存取 DependencyObject 的相依性屬性不會比存取 CLR 屬性慢。 雖然設定屬性值會有小小的效能負荷,但取得值就像取得 CLR 屬性的值一樣快。 小小效能負荷的補償是相依性屬性可支援強大的功能,例如資料繫結、動畫、繼承和樣式。 如需詳細資訊,請參閱相依性屬性概觀。
DependencyProperty 最佳化
您應該在應用程式中非常小心地定義相依性屬性。 如果您的 DependencyProperty 只影響轉譯類型中繼資料選項,而不是其他中繼資料選項,例如 AffectsMeasure,您應該藉由覆寫其中繼資料來將其標示 。 如需覆寫或取得屬性中繼資料的詳細資訊,請參閱相依性屬性中繼資料。
如果並非所有屬性變更都會實際上影響測量、排列與轉譯,手動讓屬性變更處理常式使測量、排列與轉譯階段失效,可能會更有效率。 比方說,您可能決定只有在值大於設定的限制時,才重新轉譯背景。 在此情況下,您的屬性變更處理常式只會在值超過設定的限制時使轉譯器失效。
使 DependencyProperty 成為可繼承不是免費的
根據預設,已註冊的相依性屬性是不可繼承的。 不過,您可以明確地使任何屬性成為可繼承。 雖然這是很有用的功能,但將屬性轉換為可繼承會影響效能,因為會增加屬性失效的時間長度。
小心使用 RegisterClassHandler
雖然呼叫 RegisterClassHandler 可讓您儲存實例狀態,但請務必注意處理常式會在每個實例上呼叫,這可能會造成效能問題。 只有在應用程式要求您儲存實例狀態時,才使用 RegisterClassHandler 。
在註冊期間設定 DependencyProperty 的預設值
建立需要預設值的 DependencyProperty 時,請使用傳遞為參數的預設中繼資料,將值設定為 DependencyPropertyRegister方法。 使用這項技術,而不是在建構函式或項目的每個執行個體上設定屬性值。
使用 Register 設定 PropertyMetadata 值
建立 DependencyProperty 時,您可以選擇使用 Register 或 OverrideMetadata 方法來設定 PropertyMetadata 。 雖然您的物件可能會有靜態建構函式呼叫 OverrideMetadata,但這不是最好的解決方案,且會影響效能。 為了獲得最佳效能,請在呼叫期間將 PropertyMetadata 設定為 Register。
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—這些回呼函式調用的累積都會造成顯著的效能懲罰。 此外,在此時新增和移除處理常式會相當耗損效能,因為應用程式必須周遊整個清單才能執行這項操作。 如果您的應用程式案例永遠不會變更 SolidColorBrush,您將不必要的支付維護 Changed 事件處理程式的成本。
凍結 Freezable 可以提升效能,因為不再需要花費資源維護變更通知。 下列表格顯示簡單 SolidColorBrush 的大小,當其 IsFrozen 屬性設定為 true
時,與不是如此設定時相比。 這假設將一個筆刷套用至十個 Rectangle 物件的 Fill 屬性。
州 (縣/市) | 大小 |
---|---|
Frozen SolidColorBrush | 212 個位元組 |
Non-frozen SolidColorBrush | 972 個位元組 |
下列程式碼範例說明此概念:
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;
}
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
未凍結的 Freezable 上的已變更處理常式可能會保持物件運作
物件傳遞給Freezable 物件 Changed事件的委派實際上是對該物件的參照。 因此,Changed 事件處理常式可以讓物件保持運作時間超出預期。 在對已註冊要接聽 Freezable物件Changed 事件的物件執行清除時,務必要在釋放物件之前先移除該委派。
WPF 也會在內部連結 Changed 事件。 例如,採用 Freezable 作為值的所有相依性屬性都會自動接聽 Changed 事件。 採用 Brush 的 Fill 屬性會展示這個概念。
Brush myBrush = new SolidColorBrush(Colors.Red);
Rectangle myRectangle = new Rectangle();
myRectangle.Fill = myBrush;
Dim myBrush As Brush = New SolidColorBrush(Colors.Red)
Dim myRectangle As New Rectangle()
myRectangle.Fill = myBrush
在將 myBrush
指派給 myRectangle.Fill
時,指向 Rectangle 物件的委派將會新增至 SolidColorBrush 物件的 Changed 事件。 這表示下列程式碼實際上不會讓 myRect
符合記憶體回收資格︰
myRectangle = null;
myRectangle = Nothing
在此情況下,myBrush
仍會讓 myRectangle
保持運作,並且會在引發其 Changed 事件時回呼它。 請注意,將 myBrush
指派給新 Rectangle 的 Fill 屬性,只會將另一個事件處理程式新增至 myBrush
。
清除這些類型的物件的建議方式是,從 Fill 屬性中移除 Brush ,進而移除 Changed 事件處理程式。
myRectangle.Fill = null;
myRectangle = null;
myRectangle.Fill = Nothing
myRectangle = Nothing
使用者介面虛擬化
StackPanel 也提供一個 元素變化,可將資料繫結子內容自動「虛擬化」。 在此內容中,「虛擬化」一字係指一種技術,藉由這種技術,將可從較大量的資料項目,根據畫面上可見的項目來產生物件子集。 當在指定的時間內畫面上只能有幾個 UI 元素時,不論是就記憶體還是處理器而言,產生大量 UI 元素都會相當耗費資源。 VirtualizingStackPanel (透過 VirtualizingPanel 所提供的功能) 計算可見項目,並使用 ItemsControl 的 ItemContainerGenerator (例如 ListBox 或 ListView),只建立可見項目的元素。
作為效能最佳化,只有在這些項目的視覺物件顯示在螢幕上時才會產生或保持這些項目的視覺物件運作。 如果視覺物件不再位於控制項的可檢視區域中,可能會移除視覺物件。 這並不會與資料虛擬化混淆,在資料虛擬化中,資料物件不會全都在本機集合,而是視需要進行資料流處理。
下列表格顯示將 5000 個 TextBlock 元素新增和轉譯至 StackPanel 和 VirtualizingStackPanel經過的時間。 在此案例中,測量結果代表附加文字字串到 ItemsControl 物件之 ItemsSource 屬性,與面板項目顯示文字字串時兩者之間的時間。
主面板 | 轉譯時間 (毫秒) |
---|---|
StackPanel | 3.2.1.0 |
VirtualizingStackPanel | 46 |