title: 定时器和提醒器

description: Orleans运行时提供了两种机制,称为定时器和提醒器,使开发者能够为Grains指定定期动作。

定时器

简介

定时器用于创建无需跨越多个Grain的激活(Grain的实例化)的周期性Grain行为。它与标准.NET中的System.Threading.Timer类本质上是相同的。 此外,定时器在它所操作的Grain激活中保证单线程执行。

每个激活可以有零个或多个与之关联的定时器。运行时在它所关联的激活的运行时上下文中执行每个定时器例程。

用法

要启动一个定时器,使用Grain.RegisterTimer方法,它返回一个IDisposable引用:

  1. public IDisposable RegisterTimer(
  2. Func<object, Task> asyncCallback, // 定时器需要定期执行的函数
  3. object state, // 传递给asyncCallback的object
  4. TimeSpan dueTime, // 在第一次触发之前的等待时间
  5. TimeSpan period) // 定时器的触发周期

通过释放(Dispose)定时器来取消它。

如果Grain被停用或发生故障且其Silo崩溃,计时器将停止触发。

重要注意事项

  • 启用激活回收后,定时器回调的执行不会将激活的状态从闲置改为正在使用。这意味着定时器不能用于推迟原本闲置激活的停用。
  • 传递给Grain.RegisterTimer的周期是指,从asyncCallback返回的Task被解决的时刻到下一次asyncCallback调用应该发生的时刻所经过的时间。这不仅使对asyncCallback的连续调用不可能重叠,还使完成asyncCallback的用时可以影响asyncCallback被调用的频率。这是与System.Threading.Timer的语义的重要不同。
  • 每个asyncCallback的调用都是在单独的一轮中传递给一个激活,并且永远不会与同一激活的其他轮同时运行。但是请注意,asyncCallback的调用不是作为消息传递的,因此不受消息交织语义的影响。这意味着asyncCallback的调用应该被认为是在一个可重入Grain上运行,与该Grain的其他消息相比,其行为也是如此。

提醒器

介绍

提醒器与定时器很相似,但有几个重要的不同点:

  • 提醒器是持久化的,在几乎所有情况下(包括部分或全部集群重启)都会持续触发,除非显式取消。
  • 提醒器的“定义”被写入存储,但是,对于每个特定的触发,以及其触发时间,缺并非如此。这会带来一个副作用:如果集群在一个特定的提醒器触发时完全关闭,这次触发将会错过,只有提醒器的下一次触发才会发生。
  • 提醒器关联到一个Grain,而非某个特定的激活。
  • 如果一个Grain在提醒器触发时没有关联的激活,那这个Grain将被创建。如果一个激活变成空闲状态并已停用,关联至同一Grain的提醒器将会在下一次触发时重新激活它。
  • 提醒器是通过消息传递的,与所有其他Grain方法一样受制于交织语义。
  • 提醒器不应被用于高频率的定时任务—其周期应以分钟、小时或天为单位。

配置

提醒器是持久化的,它依靠存储来运行。 在提醒器子系统运行前,你必须指定使用哪个底层存储。 这是通过使用UseXReminderService扩展方法来配置其中一个提醒器提供者来实现的,其中X是提供者的名称,如UseAzureTableReminderService

Azure Table的配置:

  1. // TODO replace with your connection string
  2. const string connectionString = "YOUR_CONNECTION_STRING_HERE";
  3. var silo = new SiloHostBuilder()
  4. [...]
  5. .UseAzureTableReminderService(options => options.ConnectionString = connectionString)
  6. [...]

SQL:

  1. // TODO replace with your connection string
  2. const string connectionString = "YOUR_CONNECTION_STRING_HERE";
  3. const string invariant = "YOUR_INVARIANT";
  4. var silo = new SiloHostBuilder()
  5. [...]
  6. .UseAdoNetReminderService(options =>
  7. {
  8. options.ConnectionString = connectionString;
  9. options.Invariant = invariant;
  10. })
  11. [...]

如果你只需要一个提醒器的占位符实现来工作,不需要设置Azure账户或SQL数据库。这是一个仅用于开发的提醒器系统的实现:

  1. var silo = new SiloHostBuilder()
  2. [...]
  3. .UseInMemoryReminderService()
  4. [...]

用法

使用提醒器的Grain必须实现IRemindable.ReceiveReminder方法。

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

要启动一个提醒器,使用Grain.RegisterOrUpdateReminder方法,它返回一个IGrainReminder对象:

  1. protected Task<IGrainReminder> RegisterOrUpdateReminder(string reminderName, TimeSpan dueTime, TimeSpan period)
  • reminderName是一个字符串,它必须能在上下文Grain的范围内唯一地识别提醒器。
  • dueTime指定提醒器在第一次触发之前的等待时间。
  • period指定提醒器的触发周期。

因为提醒器在任何一个激活的生命周期中都会存活,所以它们必须显式取消(而不是被释放(Dispose))。可以通过调用Grain.UnregisterReminder方法来取消一个提醒器:

  1. protected Task UnregisterReminder(IGrainReminder reminder)

提醒器是由Grain.RegisterOrUpdateReminder返回的句柄对象。

IGrainReminder的实例并不保证在激活的有效期之后仍然有效。如果你想以一种持久化的方式识别提醒器,请使用提醒器名称的字符串。

如果你只有提醒器的名称且需要相应的IGrainReminder实例,请调用Grain.GetReminder方法:

  1. protected Task<IGrainReminder> GetReminder(string reminderName)

我该使用哪一个?

我们建议你在以下情况下使用定时器:

  • 在激活被停用或发生故障时,定时器停止工作也并不重要(或可以接受)。
  • 定时器的周期很小(比如秒或分钟级别)。
  • 定时器的回调可以从Grain.OnActivateAsync里或当Grain方法被调用时启动。

我们建议你在以下情况下使用提醒器:

  • 定期行为需要在激活以及任何故障中保持存活。
  • 执行不频繁的任务(比如分钟、小时或天级别)

结合使用定时器和提醒器

你可以考虑使用提醒器和定时器的组合来达成目的。 例如,如果你需要一个小分辨率的定时器,它需要在不同的激活中存活,你可以使用一个每五分钟运行一次的提醒器,其目的是唤醒一个Grain来重新启动一个可能因停用而失效的本地定时器。