在对粒度调用方法之前,首先需要对该粒度进行引用。 粒度引用是实现与相应粒度类相同的粒度接口的代理对象。 它封装目标粒度的逻辑标识(类型和唯一键)。 使用粒度引用来调用目标粒度。 每个粒度引用指向单个粒度(粒度类的单个实例),但你可以创建对同一粒度的多个独立引用。
由于粒度引用表示目标粒度的逻辑标识,因此它独立于粒度的物理位置,即使在完全系统重启后仍有效。 可以像使用任何其他 .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);
颗粒参考包含三条信息:
- 唯一标识谷物类别的谷物类型。
- 粒度键,可唯一标识该粒度类的逻辑实例。
- 粒度引用必须实现的 接口 。
注释
粒度类型和密钥构成粒度标识。
请注意,上述调用 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
它显示粒度类型名称(upcounter
和 downcounter
)和粒度类(UpCounterGrain
以及 DownCounterGrain
)。
注释
上述错误消息中的粒度类型名称, upcounter
以及 downcounter
,分别派生自粒度类名, UpCounterGrain
以及 DownCounterGrain
分别派生。 这是 Orleans 中的默认行为,可以通过向 grain 类添加 [GrainType(string)]
属性来自定义。 例如:
[GrainType("up")]
public class UpCounterGrain : IUpCounterGrain { /* as above */ }
可通过多种方式解决此歧义问题,详见以下小节。
使用独特标记接口区分粮食类型
为这些谷物提供独特的接口是消除歧义的最清晰方法。 例如,如果将接口 IUpCounterGrain
添加到 UpCounterGrain
类,并将接口 IDownCounterGrain
添加到 DownCounterGrain
类,如以下示例所示,可以通过传递 IUpCounterGrain
或 IDownCounterGrain
传递给 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
粒度。 谷物 类型 和谷物 键 的组合唯一标识一个谷物。 因此,myUpCounter
和 myDownCounter
指不同的谷物。
通过提供一个谷物类别前缀来消除谷物类型歧义
可以为 IGrainFactory.GetGrain 提供粒类名称前缀,例如:
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");
使用命名约定指定默认粮实现
当区分同一粒度接口的多个实现时,Orleans 选择实现的方法是通过约定去除接口名称中的前导“I”。 例如,如果接口名称是ICounterGrain
并且有两个实现,CounterGrain
和DownCounterGrain
,那么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"));