次の方法で共有


タイマーとアラーム

Orleans ランタイムには、グレインの定期的な動作を指定できるタイマーとアラームという 2 つのメカニズムが用意されています。

タイマー

タイマーを使用して、複数のアクティブ化 (グレインのインスタンス化) にまたがる必要がない定期的なグレイン動作を作成します。 タイマーは、標準的な .NET System.Threading.Timer クラスと同じです。 さらに、タイマーは、操作対象のグレイン アクティブ化内でシングル スレッド実行の保証を受けます。

各アクティブ化には、0 個以上のタイマーを関連付けることができます。 ランタイムは、関連付けられたアクティブ化のランタイム コンテキスト内で各タイマー ルーチンを実行します。

タイマーの使用方法

タイマーを開始するには、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の各呼び出しは、個別のターンでアクティブ化に配信され、同じアクティブ化で他のターンと同時に実行されることはありません。
  • コールバックはデフォルトで順番を入れ替えることはありません。 インターリーブを有効にするには、InterleavetrueGrainTimerCreationOptionsで設定します。
  • グレイン タイマーは、返されたChange(TimeSpan, TimeSpan) インスタンスの IGrainTimer メソッドを使用して更新できます。
  • コールバックはグレインをアクティブな状態に保ち、タイマー期間が比較的短い場合は収集を妨げる可能性があります。 これを有効にするには、KeepAlivetrueするようにGrainTimerCreationOptionsを設定します。
  • コールバックは、タイマーが破棄されるか、グレインが非アクティブ化を開始したときに取り消される CancellationToken を受け取ることができます。
  • コールバックは、それらを発生したグレイン タイマーを破棄できます。
  • コールバックは、グレイン呼び出しフィルターの対象となります。
  • 分散トレースが有効になっている場合、コールバックは分散トレースに表示されます。
  • POCO グレイン ( Grainから継承されないグレイン クラス) は、 RegisterGrainTimer 拡張メソッドを使用してグレイン タイマーを登録できます。

リマインダー

アラームはタイマーに似ていますが、いくつかの重要な違いがあります。

  • アラームは永続的であり、明示的に取り消されない限り、ほぼすべての状況 (部分的または完全なクラスターの再起動を含む) でトリガーされ続けます。
  • アラームの "定義" は、ストレージに書き込まれます。 ただし、特定の時刻を持つ各特定の発生は格納されません。 これは、特定のリマインダーのティックが予定されているときにクラスターがダウンしていると、その通知はされず、次回のリマインダーのティックのみが発生するという副作用があります。
  • アラームは、特定のアクティブ化ではなく、グレインに関連付けられます。
  • リマインダーが作動する際に、グレインにアクティブ化が関連付けられていない場合は、Orleans によりグレインのアクティブ化が作成されます。 アクティブ化がアイドル状態になり、非アクティブ化された場合、同じグレインに関連付けられているアラームにより、次のティックの時点で、グレインが再アクティブ化されます。
  • アラームの配信はメッセージを介して行われ、他のすべてのグレイン メソッドと同じインターリーブ セマンティクスの対象となります。
  • 高周波タイマーにはアラームを使用しないでください。その期間は、分、時間、または日で測定する必要があります。

構成

アラームは永続的であるため、機能するためにストレージに依存します。 アラーム サブシステムを機能させるには、使用するストレージ バッキングを指定する必要があります。 これを行うには、 Use{X}ReminderService 拡張メソッドを使用してアラーム プロバイダーのいずれかを構成します。ここで、 X はプロバイダーの名前 ( UseAzureTableReminderService など) です。

Azure Table の構成:

// 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: タイマーの期間を指定します。

アラームは 1 回のアクティブ化の有効期間中も存続するため、(破棄するのではなく) 明示的に取り消す必要があります。 Grain.UnregisterReminderを呼び出してアラームを取り消します。

protected Task UnregisterReminder(IGrainReminder reminder)

reminder は、Grain.RegisterOrUpdateReminder によって返されるハンドル オブジェクトです。

IGrainReminder のインスタンスがアクティブ化の有効期間を超えて有効であることは保証されません。 アラームを永続的に識別する場合は、アラームの名前を含む文字列を使用します。

アラームの名前のみがあり、対応する IGrainReminder インスタンスが必要な場合は、 Grain.GetReminder メソッドを呼び出します。

protected Task<IGrainReminder> GetReminder(string reminderName)

どちらを使用するか判断する

次の状況では、タイマーを使用することをお勧めします。

  • アクティベーションが解除されるか、失敗が発生した際に、タイマーが機能を停止することが問題ではない(または望ましいとされる)場合。
  • タイマーの解像度が小さい (たとえば、秒または分単位で合理的に表現可能)。
  • タイマー コールバックは、 Grain.OnActivateAsync() から、またはグレイン メソッドが呼び出されたときに開始できます。

次の状況では、アラームを使用することをお勧めします。

  • 定期的な動作がアクティブ化された後も障害にも対応できる必要がある場合。
  • 頻度の低いタスクの実行 (分、時間、または日単位で合理的に表現可能など)。

タイマーとアラームを組み合わせる

目的を実現するために、アラームとタイマーの組み合わせの使用を検討することがあります。 たとえば、アクティブ化の間で存続する必要がある解像度の小さいタイマーが必要な場合は、5 分ごとに実行されるアラームを使用できます。 その目的は、非アクティブ化のために失われた可能性のあるローカルタイマーを再起動するために、グレインを再び活性化することにあります。

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

上記のコードでは、次の処理が行われます。

  • IGrainBaseIPingGrain、およびIDisposableを実装する POCO グレインを定義します。
  • 登録から 3 秒後に 10 秒ごとに呼び出されるタイマーを登録します。
  • Pingが呼び出されると、登録の直後から 1 時間ごとにアラームが呼び出されます。
  • 登録されている場合、Dispose メソッドによりアラームが取り消されます。