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 支援其他選項。
方法簽章可以每個參數為基礎包含 ImmutableAttribute:
public interface ISummerGrain : IGrain { // `values` will not be copied. ValueTask<int> Sum([Immutable] List<int> values); }
個別的屬性和欄位都可以標記為 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 的內容來說並不常見,因此強烈建議不要採取此方法,且建議堅持位元不變性。