准备工作
推荐使用.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 Interfacepublic interface IPlayerGrain : IGrainWithGuidKey{Task<IGameGrain> GetCurrentGame();Task JoinGame(IGameGrain game);Task LeaveGame(IGameGrain game);}//an example of a Grain class implementing a Grain Interfacepublic 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 playerIPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
从Orleans客户端代码:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Grain方法调用
Orleans编程模型基于异步编程。
继续利用上一节中获取到的Grain引用,可以这样调用Grain方法:
//Invoking a grain method asynchronouslyTask 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类可以选择重写以下两个虚方法:
OnActivateAsyncOnDeactivateAsync
这两个虚方法会在每个该Grain类的实例被激活或者失活的时候,被Orleans运行时调用。通过这种方式,Grain的代码就有能力在Grain实例被初始化和被清除的时候添加新的逻辑。
如果OnActivateAsync方法在执行时抛出异常,Grain实例的激活就会失败。
一旦重写了OnActivateAsync方法,方法里的逻辑就会作为激活Grain实例的一部分逻辑,每次激活时都会被调用,而OnDeactivateAsync方法就没办法保证在所有失活的情况下都会被调用了,例如在服务器故障或其他异常情况下时。因此,应用不应使用OnDeactivateAsync方法来完成一些至关重要的操作,如将Grain状态的修改持久化到数据库。重写OnDeactivateAsync方法应该仅用来完成一些尽力而为(best effort)的操作。
