谷物参考

在对粒度调用方法之前,首先需要对该粒度进行引用。 粒度引用是实现与相应粒度类相同的粒度接口的代理对象。 它封装目标粒度的逻辑标识(类型和唯一键)。 使用粒度引用来调用目标粒度。 每个粒度引用指向单个粒度(粒度类的单个实例),但你可以创建对同一粒度的多个独立引用。

由于粒度引用表示目标粒度的逻辑标识,因此它独立于粒度的物理位置,即使在完全系统重启后仍有效。 可以像使用任何其他 .NET 对象一样使用粒度引用。 你可以将其传递给方法,将其用作方法返回值,甚至将其保存到持久性存储。

可以通过将粒度的标识传递给 IGrainFactory.GetGrain<TGrainInterface>(Type, Guid) 方法来获取粒度引用,其中 T 粒度接口是粒度接口,并且 key 是其类型内粒度的唯一键。

以下示例演示如何获取前面定义的接口的 IPlayerGrain 粒度引用。

在粒度类中:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

从 Orleans 客户端代码:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);

颗粒参考包含三条信息:

  1. 唯一标识谷物类别的谷物类型
  2. 粒度键,可唯一标识该粒度类的逻辑实例。
  3. 粒度引用必须实现的 接口

注释

粒度类型和密钥构成粒度标识

请注意,上述调用 IGrainFactory.GetGrain 仅接受这三项中的两项:

  • 由粒度引用IPlayerGrain实现的接口
  • 粒度 ,即值 playerId

尽管声明粒度引用包含粒度类型接口,但示例仅随接口一起提供Orleans。 这是因为 Orleans 在粒度接口和粒度类型之间保持映射。 当你向粮食工厂询问 IShoppingCartGrain 时,Orleans 会查阅其映射以找到相应的谷物类型,以便创建引用。 当只有一个粒度接口的实现时,这才有效。 但是,如果有多个实现,则需要在调用中 GetGrain 消除它们歧义。 有关详细信息,请参阅下一部分, 消除粒度类型解析的歧义

注释

Orleans 在编译期间为应用程序中的每个粒度接口生成粒度引用实现类型。 这些粒度引用实现继承自 Orleans.Runtime.GrainReference 类。 GetGrain 返回与所请求的粒度接口对应的生成的 Orleans.Runtime.GrainReference 实现的实例。

消除谷物类型歧义

当存在多个粒度接口的实现(如以下示例中)时, Orleans 尝试在创建粒度引用时确定预期实现。 请考虑以下示例,其中接口有两个 ICounterGrain 实现:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

以下调用 GetGrain 引发异常,因为 Orleans 不知道如何明确映射到 ICounterGrain 其中一个粒度类。

// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

System.ArgumentException将引发以下消息:

Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)

错误消息指示找到与请求的粒度接口类型匹配的粒度实现Orleans。 ICounterGrain 它显示粒度类型名称(upcounterdowncounter)和粒度类(UpCounterGrain 以及 DownCounterGrain)。

注释

上述错误消息中的粒度类型名称, upcounter 以及 downcounter,分别派生自粒度类名, UpCounterGrain 以及 DownCounterGrain 分别派生。 这是 Orleans 中的默认行为,可以通过向 grain 类添加 [GrainType(string)] 属性来自定义。 例如:

[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }

可通过多种方式解决此歧义问题,详见以下小节。

使用独特标记接口区分粮食类型

为这些谷物提供独特的接口是消除歧义的最清晰方法。 例如,如果将接口 IUpCounterGrain 添加到 UpCounterGrain 类,并将接口 IDownCounterGrain 添加到 DownCounterGrain 类,如以下示例所示,可以通过传递 IUpCounterGrainIDownCounterGrain 传递给 GetGrain<T> 调用来解析正确的粒度引用,而不是不明确的 ICounterGrain 类型。

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

// Define unique interfaces for our implementations
public interface IUpCounterGrain : ICounterGrain, IGrainWithStringKey {}
public interface IDownCounterGrain : ICounterGrain, IGrainWithStringKey {}

public class UpCounterGrain : IUpCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

public class DownCounterGrain : IDownCounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

若要创建对任一粒度的引用,请考虑以下代码:

// Get a reference to an UpCounterGrain.
ICounterGrain myUpCounter = grainFactory.GetGrain<IUpCounterGrain>("my-counter");

// Get a reference to a DownCounterGrain.
ICounterGrain myDownCounter = grainFactory.GetGrain<IDownCounterGrain>("my-counter");

注释

在前面的示例中,你创建了两个具有相同键但不同的粒度类型的粒度引用。 第一个存储在 myUpCounter 变量中的元素,引用具有 ID upcounter/my-counter 的颗粒。 存储在变量中的 myDownCounter 第二个引用 ID 的 downcounter/my-counter粒度。 谷物 类型 和谷物 的组合唯一标识一个谷物。 因此,myUpCountermyDownCounter 指不同的谷物。

通过提供一个谷物类别前缀来消除谷物类型歧义

可以为 IGrainFactory.GetGrain 提供粒类名称前缀,例如:

ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");

使用命名约定指定默认粮实现

当区分同一粒度接口的多个实现时,Orleans 选择实现的方法是通过约定去除接口名称中的前导“I”。 例如,如果接口名称是ICounterGrain并且有两个实现,CounterGrainDownCounterGrain,那么Orleans在要求引用ICounterGrain时选择CounterGrain,如以下示例所示:

/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

使用属性指定默认粒度类型

可以将属性 Orleans.Metadata.DefaultGrainTypeAttribute 添加到粒度接口,以指定该接口的默认实现的粒度类型,如以下示例所示:

[DefaultGrainType("up-counter")]
public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
/// This will refer to an instance of UpCounterGrain, due to the [DefaultGrainType("up-counter"')] attribute
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

通过提供已解析的谷物 ID 来消除谷物类型的歧义

某些IGrainFactory.GetGrain重载接受Orleans.Runtime.GrainId类型的参数。 使用这些重载时,Orleans 无需从接口类型映射到grain类型,因此没有歧义需要解析。 例如:

public interface ICounterGrain : IGrainWithStringKey
{
    ValueTask<int> UpdateCount();
}

[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}

[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
    private int _count;

    public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
// This will refer to an instance of UpCounterGrain, since "up-counter" was specified as the grain type
// and the UpCounterGrain uses [GrainType("up-counter")] to specify its grain type.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>(GrainId.Create("up-counter", "my-counter"));