Partilhar via


Temporizadores e lembretes

O Orleans tempo de execução fornece dois mecanismos, chamados temporizadores e lembretes, que permitem ao desenvolvedor especificar o comportamento periódico para grãos.

Temporizadores

Os temporizadores são usados para criar um comportamento de grão periódico que não é necessário para abranger várias ativações (instanciações do grão). Um temporizador é idêntico à classe .NET System.Threading.Timer padrão. Além disso, os temporizadores estão sujeitos a garantias de execução de thread único dentro da ativação de grão em que operam, e suas execuções são intercaladas com outras solicitações, como se o retorno de chamada do temporizador fosse um método de grão marcado com AlwaysInterleaveAttribute.

Cada ativação pode ter zero ou mais temporizadores associados a ela. O tempo de execução executa cada rotina de temporizador dentro do contexto de tempo de execução da ativação à qual está associado.

Utilização do temporizador

Para iniciar um temporizador, use o RegisterGrainTimer método, que retorna uma IDisposable referência:

protected IDisposable RegisterGrainTimer(
    Func<object, Task> callback,        // function invoked when the timer ticks
    object state,                       // object to pass to callback
    GrainTimerCreationOptions options)  // timer creation options

Para cancelar o temporizador, elimine-o.

Um temporizador deixa de ser acionado se o grão for desativado ou quando ocorrer uma falha e seu silo falhar.

Considerações importantes:

  • Quando a coleta de ativação está habilitada, a execução de um retorno de chamada do temporizador não altera o estado da ativação de ociosa para em uso. Isso significa que um temporizador não pode ser usado para adiar a desativação de ativações ociosas.
  • O período passado é Grain.RegisterGrainTimer a quantidade de tempo que passa desde o momento em que o Task retorno é resolvido até callback o momento em que a próxima invocação deve callback ocorrer. Isto não só impossibilita a sobreposição de chamadas callback sucessivas, como também faz com que o tempo callback necessário para a sua conclusão afete a frequência em que callback é invocada. Este é um desvio importante da semântica do System.Threading.Timer.
  • Cada invocação de é entregue a uma ativação em um turno separado e nunca é executada callback simultaneamente com outros turnos na mesma ativação. No entanto, as invocações não são entregues como mensagens e, portanto, callback não estão sujeitas à semântica de intercalação de mensagens. Isso significa que as invocações de se comportam como se o grão fosse reentrante e executadas simultaneamente com outras solicitações de callback grãos. Para usar a semântica de agendamento de solicitação do grão, você pode chamar um método de grão para executar o trabalho que você teria feito no callback. Outra alternativa é usar um AsyncLock ou um SemaphoreSlim. Uma explicação mais detalhada está disponível na Orleans edição #2574 do GitHub.

Lembretes

Os lembretes são semelhantes aos temporizadores, com algumas diferenças importantes:

  • Os lembretes são persistentes e continuam a ser acionados em quase todas as situações (incluindo reinicializações parciais ou totais do cluster), a menos que sejam explicitamente cancelados.
  • As "definições" de lembrete são gravadas no armazenamento. No entanto, cada ocorrência específica, com seu tempo específico, não é. Isso tem o efeito colateral de que, se o cluster estiver inativo no momento de um tick de lembrete específico, ele será perdido e apenas o próximo tick do lembrete acontecerá.
  • Os lembretes estão associados a um grão, não a qualquer ativação específica.
  • Se um grão não tiver nenhuma ativação associada a ele quando um lembrete marcar, o grão será criado. Se uma ativação ficar ociosa e for desativada, um lembrete associado ao mesmo grão reativará o grão quando ele marcar em seguida.
  • A entrega de lembretes ocorre via mensagem e está sujeita à mesma semântica de intercalação que todos os outros métodos de grão.
  • Lembretes não devem ser usados para temporizadores de alta frequência - seu período deve ser medido em minutos, horas ou dias.

Configuração

Os lembretes, sendo persistentes, dependem do armazenamento para funcionar. Você deve especificar qual backup de armazenamento usar antes que o subsistema de lembrete funcione. Isso é feito configurando um dos provedores de lembrete por meio Use{X}ReminderService de métodos de extensão, onde X é o nome do provedor, por exemplo, UseAzureTableReminderService.

Configuração da Tabela do Azure:

// TODO replace with your connection string
const string connectionString = "YOUR_CONNECTION_STRING_HERE";
var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseAzureTableReminderService(connectionString)
    })
    .Build();

SQL:

const string connectionString = "YOUR_CONNECTION_STRING_HERE";
const string invariant = "YOUR_INVARIANT";
var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseAdoNetReminderService(options =>
        {
            options.ConnectionString = connectionString; // Redacted
            options.Invariant = invariant;
        });
    })
    .Build();

Se você quiser apenas que uma implementação de espaço reservado de lembretes funcione sem precisar configurar uma conta do Azure ou um banco de dados SQL, isso lhe dará uma implementação somente de desenvolvimento do sistema de lembretes:

var silo = new HostBuilder()
    .UseOrleans(builder =>
    {
        builder.UseInMemoryReminderService();
    })
    .Build();

Importante

Se você tiver um cluster heterogêneo, onde os silos lidam com diferentes tipos de grãos (implementam interfaces diferentes), cada silo deve adicionar a configuração para Lembretes, mesmo que o silo em si não manipule lembretes.

Uso de lembretes

Um grão que usa lembretes deve implementar o IRemindable.ReceiveReminder método.

Task IRemindable.ReceiveReminder(string reminderName, TickStatus status)
{
    Console.WriteLine("Thanks for reminding me-- I almost forgot!");
    return Task.CompletedTask;
}

Para iniciar um lembrete, use o Grain.RegisterOrUpdateReminder método, que retorna um IGrainReminder objeto:

protected Task<IGrainReminder> RegisterOrUpdateReminder(
    string reminderName,
    TimeSpan dueTime,
    TimeSpan period)
  • reminderName: é uma cadeia de caracteres que deve identificar exclusivamente o lembrete dentro do escopo do grão contextual.
  • dueTime: especifica uma quantidade de tempo de espera antes de emitir o tick do primeiro temporizador.
  • period: especifica o período do temporizador.

Como os lembretes sobrevivem ao tempo de vida de qualquer ativação, eles devem ser explicitamente cancelados (em vez de serem descartados). Você cancela um lembrete ligando para Grain.UnregisterReminder:

protected Task UnregisterReminder(IGrainReminder reminder)

O reminder é o objeto handle retornado por Grain.RegisterOrUpdateReminder.

Não é garantido que as instâncias de IGrainReminder sejam válidas além da vida útil de uma ativação. Se desejar identificar um lembrete de uma forma que persista, use uma cadeia de caracteres contendo o nome do lembrete.

Se você tiver apenas o nome do lembrete e precisar da instância correspondente do IGrainReminder, chame o Grain.GetReminder método:

protected Task<IGrainReminder> GetReminder(string reminderName)

Decida qual usar

Recomendamos que utilize temporizadores nas seguintes circunstâncias:

  • Se não importa (ou é desejável) que o temporizador deixe de funcionar quando a ativação é desativada ou ocorrem falhas.
  • A resolução do temporizador é pequena (por exemplo, razoavelmente expressável em segundos ou minutos).
  • O retorno de chamada do temporizador pode ser iniciado a partir de Grain.OnActivateAsync() ou quando um método grain é invocado.

Recomendamos que você use lembretes nas seguintes circunstâncias:

  • Quando o comportamento periódico precisa sobreviver à ativação e a eventuais falhas.
  • Executar tarefas pouco frequentes (por exemplo, razoavelmente expressáveis em minutos, horas ou dias).

Combine temporizadores e lembretes

Você pode considerar usar uma combinação de lembretes e temporizadores para atingir seu objetivo. Por exemplo, se você precisar de um temporizador com uma resolução pequena que precise sobreviver em ativações, você pode usar um lembrete que é executado a cada cinco minutos, cujo objetivo é despertar um grão que reinicia um temporizador local que pode ter sido perdido devido à desativação.

Registos de cereais POCO

Para registrar um temporizador ou lembrete com um grão POCO, implemente a IGrainBase interface e injete o ITimerRegistry ou IReminderRegistry no construtor do grão.

using Orleans.Timers;

namespace Timers;

public sealed class PingGrain : IGrainBase, IPingGrain, IDisposable
{
    private const string ReminderName = "ExampleReminder";

    private readonly IReminderRegistry _reminderRegistry;

    private IGrainReminder? _reminder;

    public  IGrainContext GrainContext { get; }

    public PingGrain(
        ITimerRegistry timerRegistry,
        IReminderRegistry reminderRegistry,
        IGrainContext grainContext)
    {
        // Register timer
        timerRegistry.RegisterGrainTimer(
            grainContext,
            callback: static async (state, cancellationToken) =>
            {
                // Omitted for brevity...
                // Use state

                await Task.CompletedTask;
            },
            state: this,
            options: new GrainTimerCreationOptions
            {
                DueTime = TimeSpan.FromSeconds(3),
                Period = TimeSpan.FromSeconds(10)
            });

        _reminderRegistry = reminderRegistry;

        GrainContext = grainContext;
    }

    public async Task Ping()
    {
        _reminder = await _reminderRegistry.RegisterOrUpdateReminder(
            callingGrainId: GrainContext.GrainId,
            reminderName: ReminderName,
            dueTime: TimeSpan.Zero,
            period: TimeSpan.FromHours(1));
    }

    void IDisposable.Dispose()
    {
        if (_reminder is not null)
        {
            _reminderRegistry.UnregisterReminder(
                GrainContext.GrainId, _reminder);
        }
    }
}

O código anterior:

  • Define um grão POCO que implementa IGrainBase, IPingGraine IDisposable.
  • Registra um temporizador que é invocado a cada 10 segundos e inicia 3 segundos após o registro.
  • Quando Ping é chamado, registra um lembrete que é invocado a cada hora e começa imediatamente após o registro.
  • O Dispose método cancela o lembrete se ele estiver registrado.