共用方式為


Orleans 中不可變型別的序列化

Orleans 具有一項功能,在將含有不可變型別的訊息序列化時,可用來避免連帶導致的部分額外負荷。 本節說明此功能及其應用方式,先從其相關的內容開始介紹。

Orleans 中的序列化

叫用 Grain 方法時,Orleans 執行階段會對方法引數進行深層複製,並從複本形成要求。 如此在資料傳遞至所呼叫的 Grain 之前,能避免呼叫程式碼修改引數物件。

如果呼叫的 Grain 位於不同的 Silo 上,則最終會將複本序列化為位元組資料流,並透過網路傳送至目標的 Silo,並在其中還原序列化為物件。 如果呼叫的 Grain 位於相同的 Silo 上,則會將複本直接傳遞給所呼叫的方法。

傳回值會以相同方式處理:先複製,再來則可能是加以序列化及還原序列化。

請注意,複製、序列化和還原序列化這 3 個處理程序,都會顧及物件識別。 換句話說,如果將內有相同物件的清單傳遞兩次,在接收端會取得兩次內有相同物件的清單,而不是收到兩個具有相同值的物件。

複製作業最佳化

在許多情況下,不需要深層複製。 舉例來說,有一種可能的情境便是 Web 前端從其用戶端接收位元組陣列,並將該要求 (包括位元組陣列) 傳遞至 Grain 上進行處理。 前端處理程序將陣列傳遞至 Grain 之後,不會對陣列執行任何動作;尤其不會重複使用陣列接收未來的要求。 在 Grain 內,會剖析位元組陣列以擷取輸入資料,但未做修改。 Grain 會傳回其所建立的另一個位元組陣列,以傳回給 Web 用戶端,而在傳回陣列時便會立即捨棄陣列。 Web 前端會將所產生的位元組陣列傳回至其用戶端,而不做修改。

在這種情況下,不需要複製要求或回應位元組陣列。 可惜的是,Orleans 執行階段本身無法找出此問題,因為它無法分辨 Web 前端或 Grain 稍後是否會修改陣列。 在最好的情況下,即是設置某種 .NET 機制,指出不再修改某個值:由於這次並未設置,因而為此新增 Orleans 特有機制:Immutable<T> 包裝函式類別和 ImmutableAttribute

使用 [Immutable] 屬性將型別、參數、屬性或欄位設為不可變

若為使用者定義型別,可以將 ImmutableAttribute 新增至型別。 這會對 Orleans 的序列化程式下達指示,避免複製此型別的執行個體。 下列程式碼片段示範如何使用 [Immutable] 來表示不可變的型別。 此型別不會在傳輸期間複製。

[Immutable]
public class MyImmutableType
{
    public int MyValue { get; }

    public MyImmutableType(int value)
    {
        MyValue = value;
    }
}

有時候,您可能無法控制物件,例如,您在 Grain 之間傳送的可能是 List<int>。 其他時候,您的物件有部分是不可變,其他部分則是可變。 針對這些案例,Orleans 支援其他選項。

  1. 方法簽章可以每個參數為基礎包含 ImmutableAttribute

    public interface ISummerGrain : IGrain
    {
      // `values` will not be copied.
      ValueTask<int> Sum([Immutable] List<int> values);
    }
    
  2. 個別的屬性和欄位都可以標記為 ImmutableAttribute,以防止在複製包含型別的執行個體時製作出屬性和欄位的複本。

    [GenerateSerializer]
    public sealed class MyType
    {
        [Id(0), Immutable]
        public List<int> ReferenceData { get; set; }
    
        [Id(1)]
        public List<int> RunningTotals { get; set; }
    }
    

使用 Immutable<T>

Immutable<T> 包裝函式類別可用來指出某個值可能會視為不可變;也就是將不會修改基礎值,因此不需要複製也能安全共用。 請注意,使用 Immutable<T> 表示該值的提供者和接收者未來都不會修改該值;這不是單方面的承諾,而是相互的雙方面承諾。

若要在 Grain 介面中使用 Immutable<T>,請不要傳遞 T,而請傳遞 Immutable<T>。 例如在上述案例中,Grain 方法如下:

Task<byte[]> ProcessRequest(byte[] request);

接著會變成:

Task<Immutable<byte[]>> ProcessRequest(Immutable<byte[]> request);

若要建立 Immutable<T>,只需使用建構函式:

Immutable<byte[]> immutable = new(buffer);

若要取得不可變項目內部的值,請使用 .Value 屬性:

byte[] buffer = immutable.Value;

Orleans 中的不變性

為了 Orleans 目的,不變性是相當嚴格的陳述式:修改資料項目的內容時,不會採取任何可能會變更該項目語意上的意義,或是會干擾另一個同時存取項目的執行緒的方式。 最安全的保證方式,就是完全不要修改該項目:維持位元不變性,而不是邏輯不變性。

在某些情況下,可以放心將此放寬為邏輯不變性,但請務必小心確保變動程式碼正確具備安全執行緒。 由於處理多執行緒很複雜,且以 Orleans 的內容來說並不常見,因此強烈建議不要採取此方法,且建議堅持位元不變性。