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 oTask
retorno é resolvido atécallback
o momento em que a próxima invocação devecallback
ocorrer. Isto não só impossibilita a sobreposição de chamadascallback
sucessivas, como também faz com que o tempocallback
necessário para a sua conclusão afete a frequência em quecallback
é 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 decallback
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 nocallback
. Outra alternativa é usar umAsyncLock
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,
IPingGrain
e 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.