什么是Grain客户端?
术语“客户”或有时“GrainClient”用于与其交互Grain
但本身不是Grain
逻辑的一部分的应用程序代码。客户端代码在Orleans
称为托管Silo
位置的服务器集群之外运行Grain
。因此,客户端充当集群和所有Grain
应用程序的连接器或通道。
通常,客户端用于前端Web服务器以连接到Orleans
作为Grain
执行业务逻辑的中间层的集群。在典型的设置中,前端Web服务器:
- 接收网络请求
- 执行必要的认证和授权验证
- 决定哪些谷物应该处理请求
- 使用Grain Client对谷物进行一次或多次方法调用
- 处理谷物调用和任何返回值的成功完成或失败
- 发送网络请求的响应
Grain Client的初始化
在Grain
客户端可用于调用Grain
托管在Orleans
集群中之前,需要对客户端进行配置,初始化并连接到集群。
通过一个ClientConfiguration
对象提供配置,该对象包含用于以编程方式配置客户端的配置属性层次结构。还有一种通过XML文件配置客户端的方法,但该选项将来会被弃用。更多信息在客户端配置指南中。在这里,我们将简单地使用一个助手方法来创建一个硬编码的配置对象,用于连接到本地Silo
运行localhost
。
ClientConfiguration clientConfig = ClientConfiguration.LocalhostSilo();
一旦我们有了一个配置对象,我们就可以通过这个ClientBuilder
类建立一个客户端。
IClusterClient client = new ClientBuilder().UseConfiguration(clientConfig).Build();
最后,我们需要调用Connect()
构造的客户端对象上的方法,使其连接到Orleans
集群。这是一个返回a的异步方法Task
。所以我们需要等一个await
or 完成它.Wait()
。
await client.Connect();
调用Grain
从客户端调用Grain方法没啥特殊的地方,从内作出这样的调用Grain
代码。在这两种情况下使用同样的GetGrain<T>(key)
方法(T
目标Grain
接口在哪里)来获取Grain
引用。细微的区别在于我们调用的工厂对象GetGrain
。在客户端代码中,我们通过连接的客户端对象来实现。
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
Task t = player.JoinGame(game)
await t;
对一个Grain
方法的调用返回一个Task
或一个Task<T>
按照粮食接口规则的要求。客户端可以使用await
关键字异步等待返回Task
而不阻塞线程,或者在某些情况下Wait()
阻止当前执行线程的方法。
Grain
从客户端代码和另一个客户端代码进行调用的主要区别在于Grain
单线程执行模型Grain
。谷物被Orleans
运行时限制为单线程,而客户端可能是多线程的。 Orleans
在客户端没有提供任何这样的保证,因此使用任何适合其环境的同步构造 - 锁,事件Tasks
等,由客户来管理自己的并发。
接收通知
有些情况下,简单的请求 - 响应模式不够,客户端需要接收异步通知。例如,用户可能希望在她正在关注的某人发布新消息时收到通知。
观察者就是这样一种机制,可以使客户端对象暴露在类似于Grain
目标的对象中以被调用Grain
。呼叫观察员不会提供成功或失败的任何指示,因为它们是作为单向最佳努力消息发送的。因此,必要时,应用程序代码负责在观察者之上构建更高级别的可靠性机制。
另一种可用于向客户端传递异步消息的机制是<a href="http://dotnet.github.io/orleans/Documentation/%3Ccode%20class=" defaulcode"="" style="background-color: transparent; color: rgb(51, 122, 183); cursor: pointer;">Orleans-Streams / index.html“> Streams。Streams揭示单个消息传递成功或失败的迹象,并因此启用可靠的通信回到客户端。
例子
下面是一个客户端应用程序的示例的扩展版本,该应用程序连接到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)
{
RunWatcher().Wait();
// Block main thread so that the process doesn't exit.
// Updates arrive on thread pool threads.
Console.ReadLine();
}
static async Task RunWatcher()
{
try
{
// Connect to local silo
var config = ClientConfiguration.LocalhostSilo();
var client = new ClientBuilder().UseConfiguration(config).Build();
await client.Connect();
// Hardcoded player ID
Guid playerId = new Guid("{2349992C-860A-4EDA-9590-000000000006}");
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);
IGameGrain game = null;
while (game == null)
{
Console.WriteLine("Getting current game for player {0}...", playerId);
try
{
game = await player.GetCurrentGame();
if (game == null) // Wait until the player joins a game
{
await Task.Delay(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();
await game.SubscribeForGameUpdates(
await client.CreateObjectReference<IGameObserver>(watcher));
Console.WriteLine("Subscribed successfully. Press <Enter> to stop.");
}
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>
class GameObserver : IGameObserver
{
// Receive updates
public void UpdateGameScore(string score)
{
Console.WriteLine("New game score: {0}", score);
}
}
}
}