WPF效能優化:Freezable 物件

2023-10-18 21:00:43

Freezable是WPF中一個特殊的基礎類別,用於建立可以凍結(Freeze)的可變物件。凍結一個物件意味著將其狀態設定為唯讀,從而提高效能並允許在多執行緒環境中共用物件。

Freezable的應用

我們定義畫刷資源的時候常常會這樣寫:

<SolidColorBrush x:Key="RedBrush" Color="Red" o:Freeze="True"/>

程式碼中的o:Freeze="True"其實就是使用FreezableFreeze方法凍結畫刷,使之不可修改,系統不必監視該畫刷物件,從而減少資源消耗。

o:Freeze="True"乍一看像附加屬性,其實並不是的。Freeze屬性是http://schemas.microsoft.com/winfx/2006/xaml/presentation/optionsXML名稱空間中定義的唯一屬性或其他程式設計元素。Freeze屬性專門存在於此特殊名稱空間中,以便在根元素宣告中可以使用。處理 Freeze屬性的功能專門內建於處理已編譯應用程式的 XAML的XAML處理器中。

那是不是WPF中的所有資源都可以(需要)使用Freeze方法凍結來提高效能呢?

Freezable類通常用於WPF中的資源和動畫,例如建立可重用的畫刷、幾何圖形和動畫。從Freezable繼承的型別包括BrushTransformGeometry類。由於它們包含非託管資源,因此係統必須監視這些物件發生的修改,然後在原始物件發生更改時更新對應的非託管資源。即使實際上並未修改圖形系統物件,系統仍必須消耗一些資源來監視該物件,以防更改它。

例如,假設建立一個SolidColorBrush畫筆並用它來繪製按鈕的背景。

<Window.Resources>
    <SolidColorBrush x:Key="RedBrush" Color="Red"/>
</Window.Resources>
<Button Background="{StaticResource RedBrush}"/>

呈現按鈕時,WPF圖形子系統使用你提供的資訊來繪製一組畫素,以建立按鈕的外觀。儘管使用純色畫筆來描述按鈕的繪製方式,但純色畫筆實際上並沒有進行繪製。圖形系統為按鈕和畫筆生成快速、低階別的物件,實際顯示在螢幕上的就是這些物件。
如果要修改畫筆,則必須重新生成這些低階別物件。Freezable類使畫筆能夠找到生成的相應低階別物件並在更改時更新它們。

注意事項

並非每個Freezable物件都可以凍結。為避免引發InvalidOperationException,請在嘗試凍結Freezable物件之前檢查該物件的CanFreeze屬性值,以確定是否可以將其凍結。如果滿足以下任一條件,則無法凍結Freezable:

  • 它具有動畫屬性或資料繫結屬性。
  • 它具有由動態資源設定的屬性。
  • 它包含無法凍結的Freezable子物件。

Freezable物件呼叫Freeze方法凍結後,就無法解凍。修改凍結物件屬性時會引發InvalidOperationException。但是,可以使用CloneCloneCurrentValue方法建立(深拷貝)解凍的副本。如果Freezable包含其他已凍結的 Freezable物件,它們也會被克隆並變為可修改。

無論使用哪種克隆方法,動畫都不會複製到新的 Freezable。
由於無法對凍結的Freezable進行動畫處理,因此使用Storyboard對其進行動畫處理時,動畫系統會自動建立凍結的Freezable物件的可修改克隆。為了消除克隆導致的效能開銷,如果需要對物件進行動畫處理,請讓其保持解凍狀態。

附加屬性實現XAML中Freeze

上文中提到o:Freeze="True"並不是通過附加屬性實現,而是內建於XAML處理器中實現。我們自己也可以通過附加屬性的方式實現,程式碼如下:

public class PresentationOptionsAttach
{
    public static bool GetFreeze(Freezable freezable)
    {
        return (bool)freezable.GetValue(FreezeProperty);
    }

    public static void SetFreeze(Freezable freezable, bool value)
    {
        freezable.SetValue(FreezeProperty, value);
    }

    public static readonly DependencyProperty FreezeProperty =
        DependencyProperty.RegisterAttached("Freeze", typeof(bool), typeof(PresentationOptionsAttach), new PropertyMetadata(false, OnFreezeChanged));

    private static void OnFreezeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (DesignerProperties.GetIsInDesignMode(d)) return;

        if ((bool)e.NewValue)
        {
            Freezable freezable = d as Freezable;
            if (freezable.CanFreeze)
                freezable.Freeze();
        }
    }
}

小結

Freezable是一個我們既熟悉又陌生的類,熟悉是因為我們經常使用,陌生是因為很少關注其優化效能的機制以及需要注意的地方。本文簡單介紹了Freezable優化效能的機制以及注意事項,並提供了通過附加屬性的方式在XAML中凍結資源。