Поделиться через


Таймеры и напоминания

Среда Orleans выполнения предоставляет два механизма, таймеры и напоминания, которые позволяют указать периодическое поведение для зерен.

таймеры

Используйте таймеры для создания периодического поведения зерна, которое не обязательно должно пересекать несколько активизаций (экземпляров зерна). Таймер идентичен стандартному классу .NET System.Threading.Timer . Кроме того, таймеры подвергаются гарантиям однопоточного выполнения в рамках активации зерна, с которой они работают.

Каждая активация может иметь ноль или больше таймеров, связанных с ним. Среда выполнения выполняет каждую подпрограмму таймера в контексте выполнения связанной активации.

Использование таймера

Чтобы запустить таймер, используйте RegisterGrainTimer метод, который возвращает ссылку IGrainTimer :

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

Чтобы отменить таймер, удалите его.

Таймер перестает срабатывать, если зерно деактивируется или происходит сбой и его силос ломается.

Важные рекомендации.

  • Если включена коллекция активации, выполнение обратного вызова таймера не изменяет состояние активации с простоя на использование. Это означает, что таймер нельзя использовать для отсрочки деактивации активаций, иначе находящихся в состоянии бездействия.
  • Период, передаваемый в Grain.RegisterGrainTimer, — это промежуток времени, проходящий с момента, когда Task, возвращённый callback, разрешается, до момента, когда должно произойти следующее вызовы callback. Это не только предотвращает перекрывание последовательных вызовов callback, но также означает, что время, необходимое для выполнения callback, влияет на частоту вызова callback. Это важное отклонение от семантики System.Threading.Timer.
  • Каждый вызов callback доставляется в активацию в отдельном цикле и никогда не выполняется одновременно с другими циклами той же активации.
  • Обратные вызовы по умолчанию не чередуются. Вы можете включить чередование, установив Interleave в true на GrainTimerCreationOptions.
  • Вы можете обновить таймеры зерен, используя Change(TimeSpan, TimeSpan) метод на возвращаемом IGrainTimer экземпляре.
  • Обратные вызовы могут поддерживать активность зерна, предотвращая сборку мусора, если период таймера относительно короткий. Включите это, установив KeepAlive на true в GrainTimerCreationOptions.
  • Обратные вызовы могут получать CancellationToken, который отменяется, когда таймер удаляется или зерно начинает деактивироваться.
  • Обратные вызовы могут удалить таймер зерна, который запустил их.
  • Обратные вызовы подвергаются фильтрам вызовов.
  • Обратные вызовы отображаются в распределенной трассировке при включении распределенной трассировки.
  • Зерна POCO (классы зерна, которые не наследуют от Grain) могут регистрировать таймеры зерна, используя метод расширения RegisterGrainTimer.

Напоминания

Напоминания похожи на таймеры с несколькими важными различиями:

  • Напоминания являются постоянными и продолжают запускаться практически во всех ситуациях (включая частичные или полные перезапуски кластера), если только явно не отменены.
  • Напоминание о том, что "определения" записываются в хранилище. Однако каждое конкретное вхождение с определенным временем не сохраняется. Это имеет побочный эффект, заключающийся в том, что если кластер отключен в момент запланированного события напоминания, оно будет пропущено, и произойдет только следующее событие напоминания.
  • Напоминания связаны с зерном, а не какой-либо конкретной активацией.
  • Если у зерна нет активации, связанной с ним, когда срабатывает напоминание, Orleans создает активацию зерна. Если активация становится бездействуемой и деактивирована, напоминание, связанное с тем же зерном, повторно активирует зерно, когда он будет тикать дальше.
  • Доставка напоминаний происходит через сообщение и подвергается той же семантике чередования, что и все остальные методы зерна.
  • Не следует использовать напоминания для таймеров высокой частоты; их период должен измеряться в минутах, часах или днях.

Конфигурация

Так как напоминания являются постоянными, они полагаются на хранилище для работы. Необходимо указать, какое хранилище следует использовать, прежде чем подсистема напоминаний сможет функционировать. Это можно сделать, настроив одного из поставщиков напоминаний с помощью Use{X}ReminderService методов расширения, где X имя поставщика (например, UseAzureTableReminderService).

Конфигурация таблицы 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();

Если вы просто хотите, чтобы заполнитель напоминаний работал без необходимости настраивать учетную запись Azure или базу данных SQL, это обеспечивает реализацию системы напоминаний только для разработки:

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

Это важно

Если у вас есть разнородный кластер, где силосы обрабатывают различные типы зерна (реализуют разные интерфейсы), каждый сило должен добавить конфигурацию для напоминаний, даже если само хранилище не обрабатывает напоминания.

Использование напоминаний

Зерно, использующее напоминания, должно реализовать метод IRemindable.ReceiveReminder.

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

Чтобы запустить напоминание, используйте Grain.RegisterOrUpdateReminder метод, который возвращает IGrainReminder объект:

protected Task<IGrainReminder> RegisterOrUpdateReminder(
    string reminderName,
    TimeSpan dueTime,
    TimeSpan period)
  • reminderName: это строка, которая должна однозначно идентифицировать напоминание в области контекстного зерна.
  • dueTime: указывает количество времени ожидания перед срабатыванием первого таймера.
  • period: указывает период таймера.

Так как напоминания продолжают существовать на протяжении срока службы любой отдельной активации, их необходимо отменить явно (в отличие от удаления). Отмена напоминания путем вызова Grain.UnregisterReminder:

protected Task UnregisterReminder(IGrainReminder reminder)

Объект-дескриптор reminder, который возвращает Grain.RegisterOrUpdateReminder.

Экземпляры IGrainReminder не гарантируются быть допустимыми за пределами срока действия активации. Если вы хотите постоянно идентифицировать напоминание, используйте строку, содержащую имя напоминания.

Если у вас есть только имя напоминания и требуется соответствующий экземпляр IGrainReminder, вызовите метод Grain.GetReminder.

protected Task<IGrainReminder> GetReminder(string reminderName)

Решите, какой из них следует использовать

Мы рекомендуем использовать таймеры в следующих обстоятельствах:

  • Если это не имеет значения (или желательно), что таймер перестает работать, когда активация деактивируется или происходят сбои.
  • Точность таймера низкая (например, может быть выражена в секундах или минутах).
  • Вы можете запустить обратный вызов таймера из Grain.OnActivateAsync() или при вызове метода зерна.

Рекомендуется использовать напоминания в следующих случаях:

  • Когда периодическое поведение должно сохраняться после активации и при любых сбоях.
  • Выполнение редких задач (например, которые можно выразить в минутах, часах или днях).

Объединение таймеров и напоминаний

Вы можете использовать сочетание напоминаний и таймеров для достижения цели. Например, если вам нужен таймер с небольшой точностью, который должен продолжать работать в ряде активаций, можно использовать напоминание, выполняющееся каждые пять минут. Его целью будет пробуждение зерна, которое перезапускает локальный таймер, возможно, потерян из-за деактивации.

Регистрация зерна марки POCO

Чтобы зарегистрировать таймер или напоминание с помощью зерна POCO, реализуйте IGrainBase интерфейс и внедрите ITimerRegistry или IReminderRegistry в конструктор зерна.

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);
        }
    }
}

Приведенный выше код выполняет следующие действия:

  • Определяет зерно POCO, реализующее IGrainBase, IPingGrain и IDisposable.
  • Регистрирует таймер каждые 10 секунд, начиная с 3 секунд после регистрации.
  • При вызове Ping регистрируется напоминание, которое вызывается каждый час, начиная сразу после регистрации.
  • Метод Dispose отменяет напоминание, если оно зарегистрировано.