Freigeben über


Timer und Erinnerungen

Die Orleans-Laufzeit stellt zwei Mechanismen bereit, die als Zeitgeber und Erinnerungen bezeichnet werden, mit denen der*die Entwickler ein periodisches Verhalten für Grains festlegen kann.

Timer

Zeitgeber werden verwendet, um ein periodisches Verhalten für Grains zu erstellen, das nicht mehrere Aktivierungen (Instanziierungen des Grains) umfassen muss. Ein Zeitgeber ist identisch mit der Standard-System.Threading.Timer-.NET-Klasse. Darüber hinaus gelten für Timer Ausführungsgarantien für Einzelthreads innerhalb ihrer Grainaktivierung, und ihre Ausführung wird mit anderen Anforderungen so kombiniert, als wäre der Timerrückruf eine Grainmethode, die mit AlwaysInterleaveAttribute markiert ist.

Jeder Aktivierung können null oder mehr Zeitgeber zugeordnet sein. Die Laufzeit führt jede Zeitgeberroutine innerhalb des Laufzeitkontexts der Aktivierung aus, der sie zugeordnet ist.

Verwendung des Zeitgebers

Verwenden Sie zum Starten eines Zeitgebers die Grain.RegisterTimer-Methode, die einen IDisposable-Verweis zurückgibt:

protected IDisposable RegisterTimer(
    Func<object, Task> asyncCallback, // function invoked when the timer ticks
    object state,                     // object to pass to asyncCallback
    TimeSpan dueTime,                 // time to wait before the first timer tick
    TimeSpan period)                  // the period of the timer

Um den Zeitgeber abzubrechen, müssen Sie ihn löschen.

Ein Zeitgeber wird nicht mehr ausgelöst, wenn das Grain deaktiviert wird oder wenn ein Fehler auftritt und sein Silo abstürzt.

Wichtige Überlegungen:

  • Wenn die Aktivierungssammlung aktiviert ist, ändert die Ausführung eines Zeitgeberrückrufs den Status der Aktivierung von „Leerlauf“ zu „In Verwendung“. Dies bedeutet, dass ein Zeitgeber nicht verwendet werden kann, um die Deaktivierung von Aktivierungen zu verschieben, die ansonsten im Leerlauf sind.
  • Die an Grain.RegisterTimer übergebene Zeitspanne ist die Zeitspanne, die zwischen der Beendigung der von asyncCallback zurückgegebenen Aufgabe und dem nächsten Aufruf von asyncCallback vergeht. Dies macht es nicht nur unmöglich, dass sich aufeinanderfolgende Aufrufe von asyncCallback überschneiden, sondern sorgt auch dafür, dass die Dauer der Ausführung von asyncCallback die Häufigkeit des Aufrufs von asyncCallback beeinflusst. Dies ist eine wichtige Abweichung von der Semantik der System.Threading.Timer.
  • Jeder Aufruf von asyncCallback wird einer Aktivierung in einer separaten Phase übermittelt und läuft nie gleichzeitig mit anderen Phasen derselben Aktivierung ab. asyncCallback-Aufrufe werden jedoch nicht als Nachrichten übermittelt und unterliegen daher keiner Nachrichten-Interleavingsemantik. Das bedeutet, dass Aufrufe von asyncCallback sich so verhalten, als ob der Grain eintrittsinvariant wäre und gleichzeitig mit anderen Grain-Anforderungen ausgeführt wird. Um die Planungssemantik der Anforderung des Grains zu nutzen, können Sie eine Grainmethode aufrufen, um die Arbeit auszuführen, die Sie in asyncCallback erledigt hätten. Eine weitere Alternative ist die Verwendung eines AsyncLock oder eines SemaphoreSlim. Eine ausführlichere Erläuterung finden Sie im Orleans-GitHub-Issue #2574.

Reminders

Erinnerungen ähneln Zeitgebern, mit einigen wichtigen Unterschieden:

  • Erinnerungen sind beständig und werden in fast allen Situationen ausgelöst (einschließlich teilweiser oder vollständiger Clusterneustarts), sofern sie nicht ausdrücklich abgebrochen werden.
  • „Erinnerungsdefinitionen“ werden in den Speicher geschrieben. Dies ist bei bestimmten Vorkommen mit spezifischen Zeiten jedoch nicht der Fall. Dies hat den Nebeneffekt, dass, wenn der Cluster zum Zeitpunkt einer bestimmten Erinnerungsmeldung nicht verfügbar ist, diese verpasst wird und nur die nächste Erinnerungsmeldung ausgeführt wird.
  • Erinnerungen sind mit einem Grain verknüpft, nicht mit einer bestimmten Aktivierung.
  • Wenn einem Grain zum Zeitpunkt der Erinnerung keine Aktivierung zugeordnet ist, wird das Grain erstellt. Wenn eine Aktivierung im Leerlauf ist und deaktiviert wird, reaktiviert eine Erinnerung, die mit demselben Grain verknüpft ist, das Grain, wenn sie das nächste Mal ausgelöst wird.
  • Die Erinnerungsübermittlung erfolgt über eine Nachricht und unterliegt der gleichen Interleavingsemantik wie alle anderen Grainmethoden.
  • Erinnerungen sollten nicht für Zeitgeber mit hoher Häufigkeit verwendet werden – ihr Zeitraum sollte in Minuten, Stunden oder Tagen gemessen werden.

Konfiguration

Da Erinnerungen beständig sind, müssen sie gespeichert werden, um zu funktionieren. Sie müssen angeben, welche Speicherunterstützung verwendet werden soll, bevor das Erinnerungssubsystem funktionieren kann. Dazu wird einer der Erinnerungsanbieter über Use{X}ReminderService-Erweiterungsmethoden konfiguriert, wobei X der Name des Anbieters ist (z. B. UseAzureTableReminderService).

Azure Table-Konfiguration:

// 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();

Wenn Sie nur eine Platzhalterimplementierung von Erinnerungen benötigen, ohne ein Azure-Konto oder eine SQL-Datenbank einrichten zu müssen, dann erhalten Sie eine reine Entwicklungsimplementierung des Erinnerungssystems:

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

Verwendung von Erinnerungen

Ein Grain, das Erinnerungen verwendet, muss die IRemindable.ReceiveReminder-Methode implementieren.

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

Verwenden Sie zum Starten einer Erinnerung die Grain.RegisterOrUpdateReminder-Methode, die ein IGrainReminder-Objekt zurückgibt:

protected Task<IGrainReminder> RegisterOrUpdateReminder(
    string reminderName,
    TimeSpan dueTime,
    TimeSpan period)
  • reminderName: Eine Zeichenfolge, die die Erinnerung innerhalb des Kontextgrains eindeutig identifizieren muss.
  • dueTime: Gibt eine Zeitmenge an, die gewartet werden soll, bevor der erste Zeitgeber ausgelöst werden soll.
  • period: Gibt den Zeitraum des Zeitgebers an.

Da Erinnerungen die Lebensdauer einer einzelnen Aktivierung überdauern, müssen sie explizit abgebrochen werden (und nicht entfernt) werden. Sie brechen eine Erinnerung ab, indem Sie Grain.UnregisterReminder anrufen:

protected Task UnregisterReminder(IGrainReminder reminder)

Das reminder ist das Handleobjekt, das von Grain.RegisterOrUpdateReminder zurückgegeben wird.

Bei Instanzen von IGrainReminder ist nicht garantiert, dass sie über die Lebensdauer einer Aktivierung hinaus gültig sind. Wenn Sie eine Erinnerung dauerhaft kennzeichnen möchten, verwenden Sie eine Zeichenfolge, die den Namen der Erinnerung enthält.

Wenn Sie nur den Namen der Erinnerung haben und die entsprechende Instanz von IGrainReminder benötigen, rufen Sie die Methode Grain.GetReminder auf:

protected Task<IGrainReminder> GetReminder(string reminderName)

Entscheidung über die Verwendung

Es wird empfohlen, Zeitgeber unter folgenden Umständen zu verwenden:

  • Wenn es keine Rolle spielt (oder erwünscht ist), dass der Zeitgeber nicht weiter funktioniert, wenn die Aktivierung deaktiviert wird oder Fehler auftreten
  • Die Auflösung des Zeitgebers ist klein (z. B. sinnvoll in Sekunden oder Minuten ausdrückbar)
  • Der Zeitgeberrückruf kann von Grain.OnActivateAsync() aus gestartet werden oder wenn eine Grainmethode aufgerufen wird.

Es wird empfohlen, Erinnerungen unter folgenden Umständen zu verwenden:

  • Wenn das periodische Verhalten die Aktivierung und eventuelle Ausfälle überstehen muss.
  • Bei der Durchführung unregelmäßiger Aufgaben (die z. B. im Abstand von Minuten, Stunden oder Tagen ausgeführt werden)

Kombinieren von Zeitgebern und Erinnerungen

Sie können eine Kombination aus Erinnerungen und Zeitgebern verwenden, um Ihr Ziel zu erreichen. Wenn Sie beispielsweise einen Zeitgeber mit einer kleinen Auflösung benötigen, der mehrere Aktivierungen überdauern muss, können Sie eine Erinnerung verwenden, die alle fünf Minuten ausgeführt wird und deren Zweck es ist, einen Grain zu aktivieren, der einen lokalen Zeitgeber neu startet, der aufgrund einer Deaktivierung verloren gegangen sein kann.

POCO-Grainregistrierungen

Um einen Timer oder eine Erinnerung mit einem POCO-Grain zu registrieren, implementieren Sie die IGrainBase-Schnittstelle und fügen das ITimerRegistry oder das IReminderRegistry in den Konstruktor des Grains ein.

using Orleans.Runtime;
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.RegisterTimer(
            grainContext,
            asyncCallback: static async state =>
            {
                // Omitted for brevity...
                // Use state

                await Task.CompletedTask;
            },
            state: this,
            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);
        }
    }
}

Der vorangehende Code:

  • Definiert ein POCO-Grain, das IGrainBase, IPingGrainund IDisposable implementiert
  • Registriert einen Timer, der alle zehn Sekunden aufgerufen wird, und startet drei Sekunden nach der Registrierung
  • Wenn Ping aufgerufen wird, registriert er eine Erinnerung, die jede Stunde aufgerufen wird und unmittelbar nach der Registrierung beginnt
  • Die Dispose-Methode bricht die Erinnerung ab, wenn sie registriert ist