共用方式為


計時器和提醒

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 的每個叫用都會在不同的回合傳遞至啟用,而且永遠不會與相同啟用上的其他回合同時執行。
  • 回呼函式預設不會交錯執行。 在 GrainTimerCreationOptions 中,將 Interleave 設置為 true,即可啟用交錯功能。
  • 可以使用在傳回的 IGrainTimer 實例上,使用 Change(TimeSpan, TimeSpan) 方法來更新 Grain 計時器。
  • 回呼可以讓粒紋保持作用中,避免在定時器期間相對較短時收集。 您可以將 GrainTimerCreationOptions 上的 KeepAlive 設定為 true,即可啟用此功能。
  • 回調可以接收 CancellationToken,當定時器被處置或粒紋開始停用時,該 CancellationToken 會被取消。
  • 回呼可以處置引發它們的粒紋定時器。
  • 回呼受限於細粒度呼叫篩選條件。
  • 啟用分散式追蹤時,回呼會在分散式追蹤中可見。
  • POCO 粒紋(不繼承自 Grain 的粒紋類別)可以使用 RegisterGrainTimer 擴充方法來註冊粒紋定時器。

提醒

提醒類似於計時器,有一些重要的差異:

  • 提醒是持續性的,而且在幾乎所有情況下都會繼續觸發 (包括部分或完整叢集重新啟動),除非明確取消。
  • 提醒「定義」會寫入儲存體。 不過,每個特定發生次數及其特定時間則不會寫入。 這會產生副作用,如果叢集在特定提醒刻度時關閉,則會遺漏,而且只會發生提醒的下一個刻度。
  • 提醒會與精細度相關聯,而不是任何特定啟用。
  • 如果精細度在提醒刻度時沒有與其相關聯的啟用,則會建立精細度。 如果啟用變成閒置且已停用,則與相同精細度相關聯的提醒會在下次刻度時重新啟用精細度。
  • 提醒是透過訊息發生傳遞,且受限於與所有其他精細度方法相同的交錯語意。
  • 提醒不應該用於高頻率計時器 - 其期間應以分鐘、小時或天為單位來測量。

組態

提醒,持續且依賴儲存體才能運作。 您必須指定要使用的儲存體備份,提醒子系統才能運作。 做法是透過 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)

reminderGrain.RegisterOrUpdateReminder 所傳回的控制代碼物件。

IGrainReminder 的執行個體不保證在超過啟用的生命週期後仍然有效。 如果您想要以持續的方式識別提醒,請使用包含提醒名稱的字串。

如果您只有提醒的名稱,而且需要 IGrainReminder 的對應執行個體,請呼叫 Grain.GetReminder 方法:

protected Task<IGrainReminder> GetReminder(string reminderName)

決定要使用哪一個

建議您在下列情況下使用計時器:

  • 如果計時器不重要 (或不需要) 時,則在啟用已停用或發生失敗時,計時器會停止運作。
  • 計時器解析度很小 (例如,以秒或分鐘合理表達)。
  • 可以從 Grain.OnActivateAsync() 或在叫用精細度方法時,啟動計時器回呼。

建議您在下列情況下使用提醒:

  • 當定期行為需要在啟用和任何失敗下存留時。
  • 執行不頻繁的工作 (例如,以分鐘、小時或天合理表達)。

合併計時器和提醒

您可以考慮使用提醒和計時器的組合來完成您的目標。 例如,如果您需要一個必須跨啟用存留的小型解析度計時器,您可以使用每隔五分鐘執行一次的提醒,其目的是要喚醒精細度,重新啟動可能由於停用而遺失的本機計時器。

POCO 精細度註冊

若要向 POCO 精細度註冊計時器或提醒,您可以實作 IGrainBase 介面,並將 ITimerRegistryIReminderRegistry 插入至精細度的建構函式中。

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

上述 程式碼:

  • 定義可實作 IGrainBaseIPingGrainIDisposable 的 POCO 精細度。
  • 註冊每 10 秒叫用一次的計時器,並在註冊的 3 秒後啟動。
  • 呼叫 Ping 時,註冊每小時叫用的提醒,並在註冊後立即啟動。
  • 如果已註冊提醒,則 Dispose 方法會取消提醒。