如何从服务结构actor获取状态而不等待其他方法完成?

时间:2017-02-11 00:07:18

标签: c# azure-service-fabric

我有一个正在运行的服务正在迭代X个actor,使用ActorProxy询问它们的状态。

对我来说很重要的是,这个服务不会被等待提醒回调中的其他一些长时间运行的方法阻止。

是否有某种方法可以调用下面的简单示例GetState(),该示例允许该方法以正确的方式完成,而不会阻止某些提醒运行。

class Actor : IMyActor{

public Task<MyState> GetState() => StateManager.GetAsync("key")
}

alterntive。

调用服务的正确方法是什么,如果它在5秒内没有回复,只需要包含。

var proxy = ActorProxy.Create<IMyActor();
var state = await proxy.GetState(); // this will wait until the actor is ready to send back the state. 

3 个答案:

答案 0 :(得分:3)

即使对于当前正在执行阻塞方法的Actors,也可以读取actor状态。演员使用IActorStateManager存储他们的状态,而IActorStateProvider又使用IActorStateProviderActorServiceActorService实例化一次StatefulServiceBase。每个分区都实例化负责托管和运行actor的IService。 actor服务的核心是StatefulService(或者更确切地说是IActorStateProvider,它是常规有状态服务使用的基类)。考虑到这一点,我们可以使用迎合我们的Actors的ActorService,就像我们使用常规服务一样,即使用基于KvsActorStateProvider的服务界面。

{{3}}(如果您使用的是持久状态,则由{{3}}实现)有两种方法可供我们使用:

Task<T> LoadStateAsync<T>(ActorId actorId, string stateName, CancellationToken cancellationToken = null);
Task<PagedResult<ActorId>> GetActorsAsync(int numItemsToReturn, ContinuationToken continuationToken, CancellationToken cancellationToken);

对这些方法的调用不受演员的影响。锁,这是有道理的,因为它们旨在支持分区上的所有actor。

示例:

创建自定义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

在单独的服务中,我们现在可以为Actors调用SetCountAsync来生成一些状态数据:

    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);
        }
    }

这种方法只是无休止地循环改变actor的值。 (注意总共10个演员之间的相关性,延迟3秒和演员30秒的延迟。简单地设计这种方式以防止等待锁定的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查询所有已知的Actors(针对该分区),然后直接读取每种方式的状态&#39;绕过&#39;演员并没有被演员的方法执行所阻挡。

最终作品,一些可以调用我们的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秒进行一次状态更新,每个演员每次都忙30秒。一旦每个actor方法返回,Actor服务就会看到更新后的状态。

此示例中省略了一些内容,例如,当您在actor服务中加载状态时,我们应该防止超时。

答案 1 :(得分:0)

没有办法做到这一点。演员是单线程的。如果他们正在等待在任何actor方法内完成的长时间运行工作,那么任何其他方法(包括来自外部的方法)都必须等待。

答案 2 :(得分:0)

感谢您的所有帮助。能够以您的示例为例,并进行了一些调整。我唯一的问题是将数据传递回原始应用程序服务时遇到未知类型。正在

“不希望使用ArrayOfKeyValueOflonglong。将任何静态未知的类型添加到已知类型的列表中-例如,通过使用KnownTypeAttribute属性或将它们添加到传递给DataContractSerializer的已知类型的列表中”

因此,我将GetCountsAsync的Return类型更改为List ,并且在我的类中使用了DataContract和DataMember类属性,它可以正常工作。从分区中的许多参与者检索状态数据的功能似乎应该是参与者服务的核心部分,并且您不必创建自定义参与者服务来获取StateProvider信息。再次感谢您!