一旦我们实现了grain类型,我们就可以写一个客户端应用来说使用这个类型。
下面的来自[SDK-ROOT]\Binaries\PresenceClient_ or _[SDK-ROOT]\Samples\References
目录的Orleans DLL需要引入到客户端的应用工程中:
- Orleans.dll
- OrleansRuntimeInterfaces.dll
几乎任何客户端都会用到grain的工厂类。
GetGrain()
用来得到一个特定ID的grain引用。
就像之前所说的,grain不能够显式地创建和销毁。
GrainClient.Initialize();
// 硬编码了玩家ID
Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = GrainClient.GrainFactory.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = player.CurrentGame.Result;
var watcher = new GameObserver();
var observer = GrainClient.GrainFactory.CreateObjectReference<IGameObserver>(watcher);
await game.SubscribeForGameUpdates();
如果这段代码用在控制台应用的主线程,你需要在game.SubscribeForGameUpdates()
返回后调用Wait()
,因为await
不会组织 Main()
函数返回,这样会使进程退出。
阅读关键概念部分来获取更多Task
执行调度和异常流的不同用法的细节。
找到或者创建grain
在通过调用GrainClient.Initialize()
创建链接后,泛型工厂类中的静态方法可以用来获取一个grain的引用,例如GrainClient.GrainFactory.GetGrain<IPlayerGrain>()
来获取PlayerGrain
。grain接口作为类参数传递给GrainFactory.GetGrain<T>()
。
向grain发送消息
客户端与grain的通信编程模型跟grain之间的通信编程模型一样。
client持有一个实现了grain接口IPlayerGrain
的grain引用。
它调用那个grain引用或者其他继承自IGrain
的grain接口的方法,并且这些方法有异步返回值Task
/Task<T>
。
当这些异步返回值求值得时候,客户端可以使用await
关键字或者ContinueWith()
方法来伫列被运行的延续体,或者使用Wait()
方法阻塞当前线程。
一个客户端与grain的通信和grain与grain的通信关键的不同是单线程执行模型。 grain被Orleans调度器线支撑单线程的,但是客户端可能是多线程的。 客户端库使用TPL线程池来管理延续提或者毁掉,并且客户端根据自身环境适用什么同步机制(锁、事件、TPL任务、等等)来自由决定使用什么方法管理它自己的并发。
收到通知
有些情况下简单的消息/响应模式是不够的,并且客户端需要收到异步通知。 例如,一个用户可能想要在他粉的某人发布新消息的时候收到通知。
观察者是一个单向实现了IGrainObserver
的单向异步接口,并且它的所有的方法都必须是void
的。
grain通过像调用grain接口的方法一样调用观察者的方法来发送一个通知给观察者,不同是观察者方法没有返回值并且grain不会以来调用结果。
Orleans运行时将会确保单向传递通知的成功。
发布通知的grain应该提供API来添加或者删除观察者。
想要订阅通知,客户端必须首先创建一个本地的实现了观察者接口的C#对象。
然后调用grain工厂的CreateObjectReference()
将C#对象转换成一个grain引用,之后会被传递给通知grain上的订阅方法。
这个模型也可以被其他的grain使用来接收异步通知。
不像客户端订阅的情况,订阅的grain只要实现观察者接口,并且把自身作为一个引用传递给自己(例如this.AsReference<IChirperViewer>
)。
举例
这里是一个上面给出的客户端应用连接Orleans的扩展版本,找到玩家账号,订阅玩家所在的游戏的会话的更新,并且打印出通知,直到程序被手动关闭。
namespace PlayerWatcher
{
class Program
{
/// <summary>
/// Simulates a companion application that connects to the game
/// that a particular player is currently part of, and subscribes
/// to receive live notifications about its progress.
/// </summary>
static void Main(string[] args)
{
try
{
GrainClient.Initialize();
// Hardcoded player ID
Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = GrainClient.GrainFactory.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = null;
while (game == null)
{
Console.WriteLine("Getting current game for player {0}...", playerId);
try
{
game = player.CurrentGame.Result;
if (game == null) // Wait until the player joins a game
Thread.Sleep(5000);
}
catch (Exception exc)
{
Console.WriteLine("Exception: ", exc.GetBaseException());
}
}
Console.WriteLine("Subscribing to updates for game {0}...", game.GetPrimaryKey());
// Subscribe for updates
var watcher = new GameObserver();
game.SubscribeForGameUpdates(GrainClient.GrainFactory.CreateObjectReference<IGameObserver>(watcher)).Wait();
// .Wait will block main thread so that the process doesn't exit.
// Updates arrive on thread pool threads.
Console.WriteLine("Subscribed successfully. Press <Enter> to stop.");
Console.ReadLine();
}
catch (Exception exc)
{
Console.WriteLine("Unexpected Error: {0}", exc.GetBaseException());
}
}
/// <summary>
/// Observer class that implements the observer interface.
/// Need to pass a grain reference to an instance of this class to subscribe for updates.
/// </summary>
private class GameObserver : IGameObserver
{
// Receive updates
public void UpdateGameScore(string score)
{
Console.WriteLine("New game score: {0}", score);
}
}
}
}