Таймеры и напоминания
Среда Orleans выполнения предоставляет два механизма, называемые таймерами и напоминаниями, которые позволяют разработчику указать периодическое поведение для зерен.
таймеры
Таймеры используются для создания периодического поведения зерна, которое не требуется для охвата нескольких активаций (экземпляров зерна). Таймер идентичен стандартному классу .NET System.Threading.Timer . Кроме того, таймеры подвергаются гарантиям однопоточного выполнения в рамках активации зерна, с которой они работают, и их выполнение переплетаются с другими запросами, как будто обратный вызов таймера был методом зерна, помеченным с AlwaysInterleaveAttribute.
Каждая активация может иметь нулевое или больше таймеров, связанных с ним. Среда выполнения выполняет каждую подпрограмму таймера в контексте среды выполнения активации, с которым она связана.
Использование таймера
Чтобы запустить таймер, используйте RegisterGrainTimer
метод, который возвращает ссылку 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
Чтобы отменить таймер, удалите его.
Таймер перестает активироваться, если зерно деактивировано или когда происходит сбой, и его сбой в сило.
Важные сведения:
- Если включена коллекция активаций, выполнение обратного вызова таймера не изменяет состояние активации с простоя на использование. Это означает, что таймер не может использоваться для отсрочки деактивации активации в противном случае бездействия.
Grain.RegisterGrainTimer
Период, передаваемый, — это время, которое проходит с моментаTask
, на который возвращается,callback
разрешается до момента, когда должно произойти следующее вызовcallback
. Это не только делает невозможными последовательные вызовыcallback
к перекрытию, но и делает его таким образом, чтобы продолжительностьcallback
времени была завершена, влияет на частотуcallback
вызова. Это важное отклонение от семантики System.Threading.Timer.- Каждое вызов
callback
доставляется в активацию в отдельном повороте и никогда не выполняется параллельно с другими включениями той же активации.callback
Однако вызовы не доставляются как сообщения и поэтому не подвергаются семантике взаимодействия сообщений. Это означает, что вызовыcallback
поведения, как если бы зерно повторно записывается и выполняется параллельно с другими запросами на зерно. Чтобы использовать семантику планирования запросов зерна, можно вызвать метод зерна для выполнения работы, которую вы выполнили бы вcallback
рамках. Другой альтернативой является использованиеAsyncLock
или a SemaphoreSlim. Более подробное объяснение доступно в Orleans проблеме GitHub #2574.
Напоминания
Напоминания похожи на таймеры с несколькими важными различиями:
- Напоминания являются постоянными и продолжают запускаться практически во всех ситуациях (включая частичные или полные перезапуски кластера), если только явно не отменено.
- Напоминания "определения" записываются в хранилище. Однако каждое конкретное вхождение, с определенным временем, не является. Это имеет побочный эффект, что если кластер отключен во время определенного галочки напоминания, он будет пропущен, и произойдет только следующий галок напоминания.
- Напоминания связаны с зерном, а не какой-либо конкретной активацией.
- Если у зерна нет активации, связанной с ней при возникновении напоминаний, создается зерно. Если активация становится бездействуемой и деактивирована, напоминание, связанное с тем же зерном, повторно активирует зерно, когда он будет тикать дальше.
- Доставка напоминаний происходит через сообщение и подвергается той же семантике переключения, что и все остальные методы зерна.
- Напоминания не следует использовать для таймеров высокой частоты. Их период должен измеряться в минутах, часах или днях.
Настройка
Напоминания, сохраняемые, полагаются на хранилище для работы. Необходимо указать, какое хранилище следует использовать перед функциями подсистемы напоминания. Это делается путем настройки одного из поставщиков напоминаний с помощью 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 интерфейс и внедряете или IReminderRegistry в ITimerRegistry конструктор зерна.
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
отменяет напоминание, если оно зарегистрировано.