即使是当前正在执行阻塞方法的 Actor,也可以读取 Actor 状态。 Actors 使用IActorStateManager 存储他们的状态,而IActorStateProvider 又使用IActorStateProvider。 IActorStateProvider 每个 ActorService 实例化一次。每个分区都会实例化负责托管和运行参与者的ActorService。 Actor 服务的核心是StatefulService(或者更确切地说是StatefulServiceBase,它是常规有状态服务使用的基类)。考虑到这一点,我们可以像使用常规服务一样使用迎合我们 Actor 的 ActorService,即使用基于 IService 的服务接口。
IActorStateProvider(如果您使用的是持久状态,则由KvsActorStateProvider 实现)有两种我们可以使用的方法:
Task<T> LoadStateAsync<T>(ActorId actorId, string stateName, CancellationToken cancellationToken = null);
Task<PagedResult<ActorId>> GetActorsAsync(int numItemsToReturn, ContinuationToken continuationToken, CancellationToken cancellationToken);
对这些方法的调用不受参与者锁的影响,这是有道理的,因为它们旨在支持分区上的所有参与者。
示例:
创建一个自定义 ActorService 并使用它来托管您的演员:
public interface IManyfoldActorService : IService
{
Task<IDictionary<long, int>> GetCountsAsync(CancellationToken cancellationToken);
}
public class ManyfoldActorService : ActorService, IManyfoldActorService
{
...
}
在Program.Main注册新的ActorService:
ActorRuntime.RegisterActorAsync<ManyfoldActor>(
(context, actorType) => new ManyfoldActorService(context, actorType)).GetAwaiter().GetResult();
假设我们有一个具有以下方法的简单 Actor:
Task IManyfoldActor.SetCountAsync(int count, CancellationToken cancellationToken)
{
Task.Delay(TimeSpan.FromSeconds(30), cancellationToken).GetAwaiter().GetResult();
var task = this.StateManager.SetStateAsync("count", count, cancellationToken);
ActorEventSource.Current.ActorMessage(this, $"Finished set {count} on {this.Id.GetLongId()}");
return task;
}
它等待 30 秒(模拟长时间运行、阻塞、方法调用),然后将状态值 "count" 设置为 int。
在一个单独的服务中,我们现在可以调用 SetCountAsync 让 Actors 生成一些状态数据:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
var actorProxyFactory = new ActorProxyFactory();
long iterations = 0;
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
iterations += 1;
var actorId = iterations % 10;
var count = Environment.TickCount % 100;
var manyfoldActor = actorProxyFactory.CreateActorProxy<IManyfoldActor>(new ActorId(actorId));
manyfoldActor.SetCountAsync(count, cancellationToken).ConfigureAwait(false);
ServiceEventSource.Current.ServiceMessage(this.Context, $"Set count {count} on {actorId} @ {iterations}");
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
}
}
此方法只是循环不断地更改演员的值。 (注意总共 10 个 Actor 之间的相关性,延迟 3 秒和 Actor 延迟 30 秒。简单地设计这种方式是为了防止等待锁定的 Actor 调用的无限累积)。每个调用也作为即发即弃的方式执行,因此我们可以在下一个actor返回之前继续更新下一个actor的状态。这是一段愚蠢的代码,它只是为了证明理论而设计的。
现在在actor服务中我们可以像这样实现GetCountsAsync方法:
public async Task<IDictionary<long, int>> GetCountsAsync(CancellationToken cancellationToken)
{
ContinuationToken continuationToken = null;
var actors = new Dictionary<long, int>();
do
{
var page = await this.StateProvider.GetActorsAsync(100, continuationToken, cancellationToken);
foreach (var actor in page.Items)
{
var count = await this.StateProvider.LoadStateAsync<int>(actor, "count", cancellationToken);
actors.Add(actor.GetLongId(), count);
}
continuationToken = page.ContinuationToken;
}
while (continuationToken != null);
return actors;
}
这使用底层的ActorStateProvider 来查询所有已知的Actor(针对该分区),然后直接读取每个“绕过”Actor 并且不被Actor 的方法执行阻塞的状态。
最后一部分,一些可以调用我们的 ActorService 并在所有分区中调用 GetCountsAsync 的方法:
public IDictionary<long, int> Get()
{
var applicationName = FabricRuntime.GetActivationContext().ApplicationName;
var actorServiceName = $"{typeof(IManyfoldActorService).Name.Substring(1)}";
var actorServiceUri = new Uri($"{applicationName}/{actorServiceName}");
var fabricClient = new FabricClient();
var partitions = new List<long>();
var servicePartitionList = fabricClient.QueryManager.GetPartitionListAsync(actorServiceUri).GetAwaiter().GetResult();
foreach (var servicePartition in servicePartitionList)
{
var partitionInformation = servicePartition.PartitionInformation as Int64RangePartitionInformation;
partitions.Add(partitionInformation.LowKey);
}
var serviceProxyFactory = new ServiceProxyFactory();
var actors = new Dictionary<long, int>();
foreach (var partition in partitions)
{
var actorService = serviceProxyFactory.CreateServiceProxy<IManyfoldActorService>(actorServiceUri, new ServicePartitionKey(partition));
var counts = actorService.GetCountsAsync(CancellationToken.None).GetAwaiter().GetResult();
foreach (var count in counts)
{
actors.Add(count.Key, count.Value);
}
}
return actors;
}
运行此代码现在将为我们提供 10 个参与者,它们每 33:d 秒更新一次状态,并且每个参与者每次忙 30 秒。当每个 Actor 方法返回时,Actor 服务就会看到更新的状态。
此示例中省略了一些内容,例如,当您在 Actor 服务中加载状态时,我们可能应该防止超时。