Skip to content

Grain timers and reminders

Orleans provides two mechanisms for periodic grain work:

  • Grain timers belong to one activation. They stop when that activation deactivates or its silo fails.
  • Reminders belong to a logical grain. Their definitions are stored and can reactivate the grain after deactivation or cluster restart.

Use a timer for frequent, activation-scoped work. Use a reminder when the schedule must survive activation changes and occasional missed ticks are acceptable.

Register timers with RegisterGrainTimer. RegisterTimer is obsolete.

public sealed class CacheGrain : Grain, ICacheGrain
{
private IGrainTimer? _timer;
public override Task OnActivateAsync(
CancellationToken cancellationToken)
{
_timer = this.RegisterGrainTimer(
Refresh,
new GrainTimerCreationOptions
{
DueTime = TimeSpan.Zero,
Period = TimeSpan.FromMinutes(1)
});
return base.OnActivateAsync(cancellationToken);
}
private Task Refresh(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}

RegisterGrainTimer returns IGrainTimer. Dispose it to stop the timer, or call Change to change its due time and period.

GrainTimerCreationOptions controls scheduling:

PropertyDefaultBehavior
DueTimeRequiredDelay before the first callback.
PeriodRequiredDelay from callback completion until the next callback.
InterleavefalseWhether callbacks can interleave with other grain requests.
KeepAlivefalseWhether timer activity extends activation lifetime.

A timer callback never overlaps itself. Orleans waits for the callback task to complete before measuring the next period. Exceptions are logged, and later ticks continue.

The callback token is canceled when the timer is disposed or the grain begins deactivating. Timer callbacks execute as grain turns, participate in call filters and tracing, and don’t interleave with other requests unless configured or allowed by the grain’s reentrancy settings.

A grain receiving reminders implements IRemindable:

public sealed class ReportGrain :
Grain,
IReportGrain,
IRemindable
{
public Task ReceiveReminder(
string reminderName,
TickStatus status)
{
return GenerateReport();
}
private Task GenerateReport() => Task.CompletedTask;
}

Register or update a reminder from the grain:

IGrainReminder reminder = await this.RegisterOrUpdateReminder(
"daily-report",
dueTime: TimeSpan.FromMinutes(1),
period: TimeSpan.FromDays(1));

Cancel it explicitly:

IGrainReminder? reminder =
await this.GetReminder("daily-report");
if (reminder is not null)
{
await this.UnregisterReminder(reminder);
}

Store the reminder name, not the IGrainReminder handle, across activations. Handles aren’t guaranteed to remain valid beyond the activation that retrieved them.

Reminder definitions are durable, but individual tick messages aren’t. If the cluster is unavailable at a scheduled time, that occurrence can be missed. The next scheduled tick still occurs. Reminder delivery follows normal grain request scheduling and can activate an inactive grain.

Reminders are intended for periods measured in minutes, hours, or days, not high-frequency scheduling. A common pattern is for a reminder to wake a grain and create a finer-grained local timer.

Reminder timing is subject to the following constraints:

  • dueTime must be greater than or equal to TimeSpan.Zero; a zero dueTime means the first tick is scheduled immediately.
  • dueTime cannot be negative or InfiniteTimeSpan.
  • period must be greater than TimeSpan.Zero.
  • period cannot be negative, zero, or InfiniteTimeSpan.
  • The runtime rejects period values below the lower bound configured by ReminderOptions.MinimumReminderPeriod (default: one minute).
  • dueTime is also bounded by the remaining DateTime range from the time of registration. A value which would place the first tick after MaxValue is rejected rather than clamped. Later occurrences are scheduled from the persisted start time and period.

There is no special period value that means “fire once and never again.” To model a one-shot reminder, create a valid reminder with a positive period, then unregister it in the first callback or after the first tick. TimeSpan.Zero and negative values are rejected by the runtime rather than treated as a one-shot schedule.

Every silo must configure a reminder provider. Production deployments should use a durable provider such as Azure Table, ADO.NET, Redis, or Cosmos DB. In-memory reminders are suitable only for local development and tests because definitions are lost when the cluster stops.

The provider-specific configuration is covered by each reminder provider package. For a compiled in-repository configuration example, see the reminder configuration snippets. When composing resources with Aspire, see Orleans and Aspire integration.

Grains implementing IGrainBase directly can use the same extension APIs. Inject ITimerRegistry or IReminderRegistry when lower-level registration is required. See POCO grains for the interface-only grain model.