Timer e promemoria
Il runtime Orleans fornisce due meccanismi, denominati timer e promemoria, che consentono allo sviluppatore di specificare il comportamento periodico per le granularità.
Timer
I timer vengono usati per creare un comportamento di granularità periodico che non è necessario per estendere più attivazioni (istanze della granularità). Un timer è identico alla classe System.Threading.Timer .NET standard. Inoltre, i timer sono soggetti a garanzie di esecuzione a thread singolo all'interno dell'attivazione granulare su cui operano e le relative esecuzioni vengono interfogliate con altre richieste, come se il callback del timer fosse un metodo di granularità contrassegnato con AlwaysInterleaveAttribute.
A ogni attivazione possono essere associati zero o più timer. Il runtime esegue ogni routine timer all'interno del contesto di runtime dell'attivazione a cui è associata.
Utilizzo del timer
Per avviare un timer, utilizzare il metodo RegisterGrainTimer
, che restituisce un riferimento IDisposable:
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
Per annullare il timer, eliminarlo.
Un timer smette di attivare se la granularità viene disattivata o quando si verifica un errore e il relativo silo si arresta in modo anomalo.
Considerazioni importanti:
- Quando la raccolta di attivazione è abilitata, l'esecuzione di un callback timer non modifica lo stato dell'attivazione da inattivo a in uso. Ciò significa che un timer non può essere usato per posticipare la disattivazione di attivazioni altrimenti inattive.
- Il periodo passato a
Grain.RegisterGrainTimer
è la quantità di tempo che passa dal momento in cuiTask
restituita dacallback
viene risolta al momento in cui deve verificarsi la chiamata successiva dicallback
. Ciò non solo rende impossibile che le chiamate successive acallback
si sovrappongano, ma fa sì che rendono anche in modo che il tempo di completamento dicallback
influisca sulla frequenza in cuicallback
viene richiamato. Si tratta di una deviazione importante dalla semantica di System.Threading.Timer. - Ogni chiamata di
callback
viene recapitata a un'attivazione su un turno separato e non viene mai eseguita simultaneamente con altre attivazioni della stessa attivazione. Tuttavia, le chiamatecallback
non vengono recapitate come messaggi e pertanto non sono soggette alla semantica di interleaving dei messaggi. Ciò significa che le chiamate dicallback
si comportano come se la granularità fosse rientrante e venisse eseguita simultaneamente con altre richieste di granularità. Per usare la semantica di pianificazione delle richieste di granularità, è possibile chiamare un metodo granulare per eseguire il lavoro svolto all'interno dicallback
. Un'altra alternativa consiste nell'usare un oggettoAsyncLock
o SemaphoreSlim. Una spiegazione più dettagliata è disponibile nel Orleansproblema n. 2574 di GitHub.
Promemoria
I promemoria sono simili ai timer, con alcune differenze importanti:
- I promemoria sono persistenti e continuano a essere attivati in quasi tutte le situazioni (inclusi i riavvii parziali o completi del cluster) a meno che non vengano annullati in modo esplicito.
- I promemoria "definizioni" vengono scritti nella risorsa di archiviazione. Invece, per ogni occorrenza specifica, con il suo tempo specifico, non avviene lo stesso. Questo ha l'effetto collaterale che se il cluster è inattivo al momento di un tick di promemoria specifico, verrà perso e si verifica solo il segno di spunta successivo del promemoria.
- I promemoria sono associati a una granularità, non a un'attivazione specifica.
- Se a una granularità non è associata alcuna attivazione quando viene eseguito un promemoria, viene creata la granularità. Se un'attivazione diventa inattiva e viene disattivata, un promemoria associato alla stessa granularità riattiva la granularità quando viene selezionata successivamente.
- Il recapito dei promemoria avviene tramite messaggio ed è soggetto alla stessa semantica di interleaving di tutti gli altri metodi granulari.
- I promemoria non devono essere usati per timer ad alta frequenza. Il periodo deve essere misurato in minuti, ore o giorni.
Impostazione
I promemoria, essendo persistenti, si basano sull'archiviazione per funzionare. È necessario specificare quale risorsa di archiviazione usare prima delle funzioni del sottosistema di promemoria. Questa operazione viene eseguita configurando uno dei provider di promemoria tramite metodi di estensione Use{X}ReminderService
, dove X
è il nome del provider, ad esempio UseAzureTableReminderService.
Configurazione tabella di 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 si vuole solo che un'implementazione segnaposto dei promemoria funzioni senza dover configurare un account di Azure o un database SQL, si ottiene un'implementazione di solo sviluppo del sistema di promemoria:
var silo = new HostBuilder()
.UseOrleans(builder =>
{
builder.UseInMemoryReminderService();
})
.Build();
Importante
Se si dispone di un cluster eterogeneo, in cui i silo gestiscono tipi di grain diversi (implementano interfacce diverse), ogni silo deve aggiungere la configurazione per i promemoria, anche se il silo stesso non gestisce alcun promemoria.
Utilizzo dei promemoria
Una granularità che utilizza i promemoria deve implementare il metodo IRemindable.ReceiveReminder.
Task IRemindable.ReceiveReminder(string reminderName, TickStatus status)
{
Console.WriteLine("Thanks for reminding me-- I almost forgot!");
return Task.CompletedTask;
}
Per avviare un promemoria, utilizzare il metodo Grain.RegisterOrUpdateReminder, che restituisce un oggetto IGrainReminder:
protected Task<IGrainReminder> RegisterOrUpdateReminder(
string reminderName,
TimeSpan dueTime,
TimeSpan period)
reminderName
: è una stringa che deve identificare in modo univoco il promemoria nell'ambito della granularità contestuale.dueTime
: specifica una quantità di tempo di attesa prima di emettere il tick del primo timer.period
: specifica il periodo del timer.
Poiché i promemoria sopravvivono alla durata di qualsiasi singola attivazione, devono essere annullati in modo esplicito (anziché essere eliminati). Annullare un promemoria chiamando Grain.UnregisterReminder:
protected Task UnregisterReminder(IGrainReminder reminder)
reminder
è l'oggetto handle restituito da Grain.RegisterOrUpdateReminder.
Le istanze di IGrainReminder
non sono sicuramente valide oltre la durata di un'attivazione. Se si vuole identificare un promemoria in modo permanente, usare una stringa contenente il nome del promemoria.
Se si dispone solo del nome del promemoria e si necessita dell'istanza corrispondente di IGrainReminder
, chiamare il metodo Grain.GetReminder:
protected Task<IGrainReminder> GetReminder(string reminderName)
Decidere quale usare
È consigliabile usare dei timer nelle circostanze seguenti:
- Se non importa (o è auspicabile) che il timer smetta di funzionare quando l'attivazione viene disattivata o si verificano errori.
- La risoluzione del timer è piccola (ad esempio, ragionevolmente esprimibili in secondi o minuti).
- Il callback del timer può essere avviato da Grain.OnActivateAsync() o quando viene richiamato un metodo granulare.
È consigliabile usare i promemoria nelle circostanze seguenti:
- Quando il comportamento periodico deve sopravvivere all'attivazione e a eventuali errori.
- Esecuzione di attività poco frequenti (ad esempio, ragionevolmente esprimibili in minuti, ore o giorni).
Combinare timer e promemoria
È possibile prendere in considerazione l'uso di una combinazione di promemoria e timer per raggiungere l'obiettivo. Ad esempio, se è necessario un timer con una piccola risoluzione che deve sopravvivere tra le attivazioni, è possibile usare un promemoria che viene eseguito ogni cinque minuti, il cui scopo è quello di riattivare una granularità che riavvia un timer locale che potrebbe essere stato perso a causa della disattivazione.
Registrazioni di granularità POCO
Per registrare un timer o un promemoria con una granularità POCO, implementare l'interfaccia IGrainBase e inserire ITimerRegistry o IReminderRegistry nel costruttore della granularità.
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);
}
}
}
Il codice precedente:
- Definisce una granularità POCO che implementa IGrainBase,
IPingGrain
e IDisposable. - Registra un timer richiamato ogni 10 secondi e inizia 3 secondi dopo la registrazione.
- Quando
Ping
viene chiamato, registra un promemoria richiamato ogni ora e inizia immediatamente dopo la registrazione. - Il metodo
Dispose
annulla il promemoria se è registrato.