在编写实现grain类的代码之前,请新建一个面向.NET Standard(首选)、或.NET Framework 4.6.1或更高版本的类库项目(如果由于依赖性而无法使用.NET Standard)。grain接口和grain类可以在同一个类库项目中定义,也可以在两个不同的项目中定义,以便更好地分离接口和实现。无论哪种情况,项目都需要引用Microsoft.Orleans.Core.Abstractions
和Microsoft.Orleans.CodeGenerator.MSBuild
NuGet包。
有关更详细的说明,请参阅Tutorial One - Orleans Basics的Project Setup部分。
grain之间能彼此交互,它能被外部调用其方法,这些方法是grain接口声明中的一部分。grain类实现一个或多个先前声明的grain接口。grain接口的所有方法都必须返回一个 Task
(对于void
方法)、一个 Task<T>
或一个 ValueTask<T>
(对于返回类型值的类型为T
的方法)。
以下是Orleans 1.5版的Presence Service示例的摘录:
//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;
}
}
返回值类型为T
的grain方法,在grain接口中定义为返回一个 Task<T>
。对于未标记async
关键字的grain的方法,当返回值可用时,通常通过以下语句返回:
public Task<SomeType> GrainMethod1()
{
...
return Task.FromResult(<variable or constant with result>);
}
一个不返回值的grain方法,实际上是一个void方法,在grain接口中定义为返回一个Task
。返回Task
表示异步执行和方法的完成。对于未标记async
关键字的grain方法,当“void”方法完成其执行时,需要返回以下特殊值Task.CompletedTask
:
public Task GrainMethod2()
{
...
return Task.CompletedTask;
}
标记为async
的grain的方法,直接返回值:
public async Task<SomeType> GrainMethod3()
{
...
return <variable or constant with result>;
}
一个“void”的grain的方法,若被标记为async
,则不返回任何值,只是在执行结束时返回:
public async Task GrainMethod4()
{
...
return;
}
如果一个grain方法,接收从另一个异步方法调用的返回值,无论此异步调用是不是grain的方法,都不需要对该调用执行错误处理,它只需简单地将从该异步调用接收的Task
,作为其返回值:
public Task<SomeType> GrainMethod5()
{
...
Task<SomeType> task = CallToAnotherGrain();
return task;
}
类似地,一个“void”的grain的方法,可以返回另一个调用返回给它的Task
,而不是await它。
public Task GrainMethod6()
{
...
Task task = CallToAsyncAPI();
return task;
}
ValueTask<T>
可以用来代替 Task<T>。
一个grain引用是一个代理对象,它实现了与对应的grain类相同的grain接口。它封装了目标grain的逻辑标识(类型和唯一键)。使用grain引用,来调用目标grain。每个grain引用只能用于单个grain(grain类的单个实例),但是可以为同一grain创建多个独立的引用。
由于grain引用表示目标grain的逻辑标识,因此它与grain的物理位置无关,即使在系统彻底重新启动后,也保持有效。开发人员可以像任何其他.NET对象一样使用grain引用。它可以传递给一个方法,用作方法的返回值等,甚至可以保存到持久存储中。
可以通过将grain的标识,传递给GrainFactory.GetGrain<T>(key)
方法,来获得grain引用,其中,T
是grain的接口,而key
是该类型中grain的唯一键。
以下演示了如何获得上文中定义的IPlayerGrain
接口的grain引用。
从grain类的内部:
//construct the grain reference of a specific player
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);
从Orleans的client:
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
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);
可以join两个或更多个Tasks
;join操作创建一个Task
,在其所有组成任务都完成之后,该Task
完成。当一个grain需要启动多个计算,并等待这些计算完成才能继续时,这种模式很有用。例如,生成网页(由多个部分组成)的前端grain,可能会产生多个后端调用,一个部分一个调用,并为每个结果接收一个Task
。然后,grain将await这些Tasks
的join; 当join的Task
完成,则各个Task
已经完成,且已经收到格式化网页所需的所有数据。
例:
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代码有机会执行额外的初始化和清理操作。若OnActivateAsync
抛出异常,则激活失败。虽然OnActivateAsync
(如果被重写)总是作为grain激活过程的一部分被调用,但OnDeactivateAsync
不保证在所有情况下都被调用,例如,在服务器故障或其他异常事件的情况下。因此,应用程序不应依赖OnDeactivateAsync
来执行关键操作,例如状态更改的持久化。应用程序应只用它于力所能及的操作。