准备工作
推荐使用.NET Standard来创建项目。Grain接口和Grain类可以定义在工程里,也可以定义在多个工程里,以便更好地区分接口和实现。不过无论如何,都需要引用这两个NuGet包:Microsoft.Orleans.Core.Abstractions和Microsoft.Orleans.CodeGenerator.MSBuild。
关于准备工作更全面的文档,见教程与样例。
Grain接口和类
Grain之间的交互和被外部调用,都使用的是定义在接口中的方法。一个Grain类可以实现一个或多个事先定义好的Grain接口。Grain接口的所有方法的返回值都必须为Task
、Task<T>
或ValueTask<T>
。
下面摘录一个示例:
//an example of a Grain Interface
public interface IPlayerGrain : IGrainWithGuidKey
{
Task<IGameGrain> GetCurrentGame();
Task JoinGame(IGameGrain game);
Task LeaveGame(IGameGrain game);
}
//an example of a Grain class implementing a Grain Interface
public class PlayerGrain : Grain, IPlayerGrain
{
private IGameGrain currentGame;
// Game the player is currently in. May be null.
public Task<IGameGrain> GetCurrentGame()
{
return Task.FromResult(currentGame);
}
// Game grain calls this method to notify that the player has joined the game.
public Task JoinGame(IGameGrain game)
{
currentGame = game;
Console.WriteLine(
"Player {0} joined game {1}",
this.GetPrimaryKey(),
game.GetPrimaryKey());
return Task.CompletedTask;
}
// Game grain calls this method to notify that the player has left the game.
public Task LeaveGame(IGameGrain game)
{
currentGame = null;
Console.WriteLine(
"Player {0} left game {1}",
this.GetPrimaryKey(),
game.GetPrimaryKey());
return Task.CompletedTask;
}
}
Grain方法的返回值
如果一个方法可以返回一个类型T
的值,在Grain接口中,应该被定义其返回类型为Task<T>
。如果Grain方法没有标记async
关键字,拿到返回值后可以这么写:
public Task<SomeType> GrainMethod1()
{
...
return Task.FromResult(<variable or constant with result>);
}
如果一个Grain方法返回值实际上是一个void,在Grain接口中就将其返回值定义为Task
。Task
表示这个方法是异步执行的,且已经执行完毕。这种情况下如果没有标记async
关键字,可以在最后返回Task.CompletedTask
:
public Task GrainMethod2()
{
...
return Task.CompletedTask;
}
而如果Grain方法实现上标记了async
关键字,就可以直接返回值:
public async Task<SomeType> GrainMethod3()
{
...
return <variable or constant with result>;
}
“void”方法的情况:
public async Task GrainMethod4()
{
...
return;
}
如果一个Grain方法异步调用了其他方法并接收了其返回值,并且不需要对这个调用做异常处理,那么就用一个Task类型的变量来接收这个返回值。
public Task<SomeType> GrainMethod5()
{
...
Task<SomeType> task = CallToAnotherGrain();
return task;
}
类似地,“void”方法的情况:
public Task GrainMethod6()
{
...
Task task = CallToAsyncAPI();
return task;
}
可以用ValueTask<T>
来代替Task<T>
。
Grain引用
Grain引用是一个代理对象(proxy object),它实现了和对应的Grain类一样的接口(应该是动态代理模式)。它为目标Grain封装了一个逻辑标识(结合类型和唯一的key)。Grain引用的作用就是调用目标Grain。每一个Grain引用对应的是一个单例的Grain,不过可以为同一个Grain创建多个不同的Grain引用(多个Grain引用实例都针对同一个Grain实例做了动态代理的意思吧)。
由于Grain引用相当于目标Grain的逻辑标识,与Grain的物理位置没什么关系,哪怕系统重启,Grain引用依然是可用的。开发者可以像使用其他.NET对象那样使用Grain引用:可以作为参数传给一个方法,作为方法的返回值,等等,甚至可以持久化到存储里。
可以通过往方法GrainFactory.GetGrain<T>(key)
里传入标识来获得一个Grain引用,其中T
是Grain接口,key
是该类型的Grain中一个唯一的key。
接下来演示一下如何获得上层定义了Grain接口IPlayerGrain
的Grain引用。
从Grain类内部:
//construct the grain reference of a specific player
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
从Orleans客户端代码:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Grain方法调用
Orleans编程模型基于异步编程。
继续利用上一节中获取到的Grain引用,可以这样调用Grain方法:
//Invoking a grain method asynchronously
Task joinGameTask = player.JoinGame(this);
//The await keyword effectively makes the remainder of the method execute asynchronously at a later point (upon completion of the Task being awaited) without blocking the thread.
await joinGameTask;
//The next line will execute later, after joinGameTask is completed.
players.Add(playerId);
可以把多个Task
结合(join)起来,结合的意义在于,创建了一个新的Task
,当被结合的Task
都被异步调用完成后,才可以解析这个(新建的)Task
。这个模式在Grain需要启动多个计算过程,且需要等这些计算过程都结束后才能继续接下来的操作时,很有用。例如,一个用来生成web页面的前端Grain可能由很多部分构成,需要调用多次后端逻辑,每一部分的调用都接收一个Task
作为结果。那么这个Grain可以等待这些Task
的结合(join Task)完成调用以后,再解析这些Task
,从而拿到所有组成web页面的所有数据。
示例:
List<Task> tasks = new List<Task>();
Message notification = CreateNewMessage(text);
foreach (ISubscriber subscriber in subscribers)
{
tasks.Add(subscriber.Notify(notification));
}
// WhenAll joins a collection of tasks, and returns a joined Task that will be resolved when all of the individual notification Tasks are resolved.
Task joinedTask = Task.WhenAll(tasks);
await joinedTask;
// Execution of the rest of the method will continue asynchronously after joinedTask is resolve.
虚方法
Grain类可以选择重写以下两个虚方法:
OnActivateAsync
OnDeactivateAsync
这两个虚方法会在每个该Grain类的实例被激活或者失活的时候,被Orleans运行时调用。通过这种方式,Grain的代码就有能力在Grain实例被初始化和被清除的时候添加新的逻辑。
如果OnActivateAsync
方法在执行时抛出异常,Grain实例的激活就会失败。
一旦重写了OnActivateAsync
方法,方法里的逻辑就会作为激活Grain实例的一部分逻辑,每次激活时都会被调用,而OnDeactivateAsync
方法就没办法保证在所有失活的情况下都会被调用了,例如在服务器故障或其他异常情况下时。因此,应用不应使用OnDeactivateAsync
方法来完成一些至关重要的操作,如将Grain状态的修改持久化到数据库。重写OnDeactivateAsync
方法应该仅用来完成一些尽力而为(best effort)的操作。