次の方法で共有


Orleans における変更できない型のシリアル化

Orleans には、変更できない型を含むメッセージのシリアル化に関連するいくつかのオーバーヘッドを回避するために使用できる機能があります。 このセクションでは、関連するコンテキストから始めて、機能とそのアプリケーションについて説明します。

Orleans でのシリアル化

グレイン メソッドを呼び出すと、 Orleans ランタイムはメソッド引数のディープ コピーを作成し、これらのコピーからの要求を形成します。 これにより、呼び出されたグレインにデータが渡される前に、引数オブジェクトを変更する呼び出し元のコードから保護されます。

呼び出されたグレインが別のサイロにある場合、コピーは最終的にバイト ストリームにシリアル化され、ネットワーク経由でターゲット サイロに送信され、そこでオブジェクトに逆シリアル化されます。 呼び出されたグレインが同じサイロにある場合、コピーは呼び出されたメソッドに直接渡されます。

戻り値は同じ方法で処理されます。最初にコピーされた後、シリアル化および逆シリアル化される可能性があります。

コピー、シリアル化、逆シリアル化の 3 つのプロセスはすべて、オブジェクト ID に優先されることに注意してください。 つまり、同じオブジェクトを含むリストを 2 回渡すと、受信側は同じ値を持つ 2 つのオブジェクトではなく、同じオブジェクトを持つリストを 2 回取得します。

コピーの最適化

多くの場合、ディープ コピーは不要です。 たとえば、Web フロントエンドがクライアントからバイト配列を受け取り、その要求 (バイト配列を含む) を処理のためにグレインに渡すシナリオを考えてみましょう。 フロントエンド プロセスは、グレインに渡した後、配列に対して何も行いません。具体的には、将来の要求に対して配列は再利用されません。 グレイン内では、バイト配列が解析されて入力データが取得されますが、変更されません。 グレインは、作成した別のバイト配列を Web クライアントに返し、配列を返した直後に破棄します。 Web フロントエンドは、結果のバイト配列を変更せずにクライアントに戻します。

このようなシナリオでは、要求または応答のバイト配列をコピーする必要はありません。 残念ながら、 Orleans ランタイムは、Web フロントエンドまたはグレインが後で配列を変更するかどうかを判断できないため、これを自動的に把握することはできません。 .NET メカニズムは、値が変更されなくなったことを示すのが理想的です。 これに欠け、 Orleans固有のメカニズム ( Immutable<T> ラッパー クラスと ImmutableAttribute) が追加されました。

[Immutable]属性を使用して、型、パラメーター、プロパティ、またはフィールドを不変としてマークします

ユーザー定義型の場合は、型に ImmutableAttribute を追加できます。 これにより、この型のインスタンスをコピーしないように、 Orleans シリアライザーに指示します。 次のコード スニペットは、 [Immutable] を使用して変更できない型を示す方法を示しています。 この型は転送中にコピーされません。

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

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

場合によっては、オブジェクトを制御できない場合があります。たとえば、グレイン間で送信する 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>を使用すると、プロバイダーも値の受信者も将来にその値を変更しないことが前提となります。 これは、片側ではなく、相互の両面コミットメントです。

グレイン インターフェイスでImmutable<T>を使用するには、Immutable<T>の代わりにTを渡します。 たとえば、上記のシナリオでは、グレインメソッドは次のようになります。

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 コンテキストでは珍しいので、このアプローチに対して強く推奨し、ビットごとの不変性を維持することをお勧めします。