Temporizadores e lembretes de ator

Os atores podem agendar trabalho periódico em si mesmos ao registar temporizadores ou lembretes. Este artigo mostra como utilizar temporizadores e lembretes e explica as diferenças entre os mesmos.

Temporizadores de ator

Os temporizadores de ator fornecem um wrapper simples em torno de um temporizador .NET ou Java para garantir que os métodos de chamada de retorno respeitam as garantias de simultaneidade baseadas na rotação fornecidas pelo runtime actors.

Os atores podem utilizar os RegisterTimermétodos (C#) ou registerTimer(Java) e UnregisterTimer(C#) ou unregisterTimer(Java) na respetiva classe base para registar e anular o registo dos respetivos temporizadores. O exemplo abaixo mostra a utilização de APIs de temporizador. As APIs são muito semelhantes ao temporizador .NET ou temporizador Java. Neste exemplo, quando o temporizador for devido, o runtime Atores chamará o MoveObjectmétodo (C#) ou moveObject(Java). O método é garantido para respeitar a simultaneidade baseada na viragem. Isto significa que não estarão em curso outros métodos de ator ou chamadas de retorno de temporizador/lembrete até que esta chamada de retorno conclua a execução.

class VisualObjectActor : Actor, IVisualObject
{
    private IActorTimer _updateTimer;

    public VisualObjectActor(ActorService actorService, ActorId actorId)
        : base(actorService, actorId)
    {
    }

    protected override Task OnActivateAsync()
    {
        ...

        _updateTimer = RegisterTimer(
            MoveObject,                     // Callback method
            null,                           // Parameter to pass to the callback method
            TimeSpan.FromMilliseconds(15),  // Amount of time to delay before the callback is invoked
            TimeSpan.FromMilliseconds(15)); // Time interval between invocations of the callback method

        return base.OnActivateAsync();
    }

    protected override Task OnDeactivateAsync()
    {
        if (_updateTimer != null)
        {
            UnregisterTimer(_updateTimer);
        }

        return base.OnDeactivateAsync();
    }

    private Task MoveObject(object state)
    {
        ...
        return Task.FromResult(true);
    }
}
public class VisualObjectActorImpl extends FabricActor implements VisualObjectActor
{
    private ActorTimer updateTimer;

    public VisualObjectActorImpl(FabricActorService actorService, ActorId actorId)
    {
        super(actorService, actorId);
    }

    @Override
    protected CompletableFuture onActivateAsync()
    {
        ...

        return this.stateManager()
                .getOrAddStateAsync(
                        stateName,
                        VisualObject.createRandom(
                                this.getId().toString(),
                                new Random(this.getId().toString().hashCode())))
                .thenApply((r) -> {
                    this.registerTimer(
                            (o) -> this.moveObject(o),                        // Callback method
                            "moveObject",
                            null,                                             // Parameter to pass to the callback method
                            Duration.ofMillis(10),                            // Amount of time to delay before the callback is invoked
                            Duration.ofMillis(timerIntervalInMilliSeconds));  // Time interval between invocations of the callback method
                    return null;
                });
    }

    @Override
    protected CompletableFuture onDeactivateAsync()
    {
        if (updateTimer != null)
        {
            unregisterTimer(updateTimer);
        }

        return super.onDeactivateAsync();
    }

    private CompletableFuture moveObject(Object state)
    {
        ...
        return this.stateManager().getStateAsync(this.stateName).thenCompose(v -> {
            VisualObject v1 = (VisualObject)v;
            v1.move();
            return (CompletableFuture<?>)this.stateManager().setStateAsync(stateName, v1).
                    thenApply(r -> {
                      ...
                      return null;});
        });
    }
}

O período seguinte do temporizador é iniciado após a chamada de retorno concluir a execução. Isto implica que o temporizador é parado enquanto a chamada de retorno está a ser executada e é iniciada quando a chamada de retorno é concluída.

O runtime Actors guarda as alterações feitas ao State Manager do ator quando a chamada de retorno terminar. Se ocorrer um erro ao guardar o estado, esse objeto de ator será desativado e será ativada uma nova instância.

Ao contrário dos lembretes, os temporizadores não podem ser atualizados. Se RegisterTimer for chamado novamente, será registado um novo temporizador.

Todos os temporizadores são parados quando o ator é desativado como parte da recolha de lixo. Depois disso, não são invocadas chamadas de retorno de temporizador. Além disso, o runtime actors não retém nenhuma informação sobre os temporizadores que estavam em execução antes da desativação. Cabe ao ator registar os temporizadores de que precisa quando for reativado no futuro. Para obter mais informações, consulte a secção sobre a recolha de lixo do ator.

Lembretes de ator

Os lembretes são um mecanismo para acionar chamadas de retorno persistentes num ator em horas especificadas. A respetiva funcionalidade é semelhante aos temporizadores. Mas, ao contrário dos temporizadores, os lembretes são acionados em todas as circunstâncias até que o ator os anule explicitamente ou o ator seja explicitamente eliminado. Especificamente, os lembretes são acionados em desativações de ator e ativações pós-falha porque o runtime actors persiste informações sobre os lembretes do ator usando o fornecedor de estado do ator. Ao contrário dos temporizadores, os lembretes existentes podem ser atualizados ao chamar novamente o método de registo (RegisterReminderAsync) com o mesmo reminderName.

Nota

A fiabilidade dos lembretes está associada às garantias de fiabilidade do estado fornecidas pelo fornecedor de estado do ator. Isto significa que, para os atores cuja persistência de estado está definida como Nenhum, os lembretes não serão acionados após uma ativação pós-falha.

Para registar um lembrete, um ator chama o RegisterReminderAsync método fornecido na classe base, conforme mostrado no exemplo seguinte:

protected override async Task OnActivateAsync()
{
    string reminderName = "Pay cell phone bill";
    int amountInDollars = 100;

    IActorReminder reminderRegistration = await this.RegisterReminderAsync(
        reminderName,
        BitConverter.GetBytes(amountInDollars),
        TimeSpan.FromDays(3),    //The amount of time to delay before firing the reminder
        TimeSpan.FromDays(1));    //The time interval between firing of reminders
}
@Override
protected CompletableFuture onActivateAsync()
{
    String reminderName = "Pay cell phone bill";
    int amountInDollars = 100;

    ActorReminder reminderRegistration = this.registerReminderAsync(
            reminderName,
            state,
            dueTime,    //The amount of time to delay before firing the reminder
            period);    //The time interval between firing of reminders
}

Neste exemplo, "Pay cell phone bill" é o nome do lembrete. Esta é uma cadeia que o ator utiliza para identificar exclusivamente um lembrete. BitConverter.GetBytes(amountInDollars)(C#) é o contexto associado ao lembrete. Será reencaminhado para o ator como um argumento para a chamada de retorno do lembrete, ou seja IRemindable.ReceiveReminderAsync, (C#) ou Remindable.receiveReminderAsync(Java).

Os atores que utilizam lembretes têm de implementar a IRemindable interface, conforme mostrado no exemplo abaixo.

public class ToDoListActor : Actor, IToDoListActor, IRemindable
{
    public ToDoListActor(ActorService actorService, ActorId actorId)
        : base(actorService, actorId)
    {
    }

    public Task ReceiveReminderAsync(string reminderName, byte[] context, TimeSpan dueTime, TimeSpan period)
    {
        if (reminderName.Equals("Pay cell phone bill"))
        {
            int amountToPay = BitConverter.ToInt32(context, 0);
            System.Console.WriteLine("Please pay your cell phone bill of ${0}!", amountToPay);
        }
        return Task.FromResult(true);
    }
}
public class ToDoListActorImpl extends FabricActor implements ToDoListActor, Remindable
{
    public ToDoListActor(FabricActorService actorService, ActorId actorId)
    {
        super(actorService, actorId);
    }

    public CompletableFuture receiveReminderAsync(String reminderName, byte[] context, Duration dueTime, Duration period)
    {
        if (reminderName.equals("Pay cell phone bill"))
        {
            int amountToPay = ByteBuffer.wrap(context).getInt();
            System.out.println("Please pay your cell phone bill of " + amountToPay);
        }
        return CompletableFuture.completedFuture(true);
    }

Quando um lembrete é acionado, o runtime reliable Actors invocará o ReceiveReminderAsyncmétodo (C#) ou receiveReminderAsync(Java) no Ator. Um ator pode registar vários lembretes e o ReceiveReminderAsyncmétodo (C#) ou receiveReminderAsync(Java) é invocado quando qualquer um desses lembretes é acionado. O ator pode utilizar o nome do lembrete que é transmitido para o ReceiveReminderAsyncmétodo (C#) ou receiveReminderAsync(Java) para descobrir que lembrete foi acionado.

O runtime Actors guarda o estado do ator quando a ReceiveReminderAsyncchamada (C#) ou receiveReminderAsync(Java) é concluída. Se ocorrer um erro ao guardar o estado, esse objeto de ator será desativado e será ativada uma nova instância.

Para anular o registo de um lembrete, um ator chama o UnregisterReminderAsyncmétodo (C#) ou unregisterReminderAsync(Java), conforme mostrado nos exemplos abaixo.

IActorReminder reminder = GetReminder("Pay cell phone bill");
Task reminderUnregistration = await UnregisterReminderAsync(reminder);
ActorReminder reminder = getReminder("Pay cell phone bill");
CompletableFuture reminderUnregistration = unregisterReminderAsync(reminder);

Conforme mostrado acima, o UnregisterReminderAsyncmétodo (C#) ou unregisterReminderAsync(Java) aceita uma IActorReminderinterface (C#) ou ActorReminder(Java). A classe base de ator suporta um GetRemindermétodo (C#) ou getReminder(Java) que pode ser utilizado para obter a IActorReminderinterface (C#) ou ActorReminder(Java) ao transmitir o nome do lembrete. Isto é conveniente porque o ator não precisa de manter a IActorReminderinterface (C#) ou ActorReminder(Java) que foi devolvida a partir da chamada do RegisterRemindermétodo (C#) ou registerReminder(Java).

Passos Seguintes

Saiba mais sobre os eventos e a reentraência do Reliable Actor: