Freigeben über


Migrieren von Orleans 3.x zu Version 7.0

In Orleans 7.0 wurden mehrere vorteilhafte Änderungen eingeführt, darunter Verbesserungen in den Bereichen Hosting, benutzerdefinierte Serialisierung, Unveränderlichkeit und Abstraktionen von Grains.

Migration

Aufgrund von Änderungen bei der Identifizierung von Orleans Grains und Datenströmen ist es derzeit nicht einfach, vorhandene Anwendungen mithilfe von Erinnerungen, Datenströmen oder Grain-Persistenz auf Version Orleans 7.0 zu migrieren.

Das reibungslose Aktualisieren auf Orleans 7.0 durch ein rollierendes Upgrade von Anwendungen, die ältere Orleans Versionen verwenden, ist nicht möglich. Verwenden Sie daher eine andere Upgradestrategie, z. B. die Bereitstellung eines neuen Clusters und das Außerbetriebsetzen des vorherigen Clusters. Orleans 7.0 ändert das Drahtprotokoll inkompatibel, was bedeutet, dass Cluster keine Mischung aus Orleans 7.0-Hosts und Hosts enthalten können, die frühere Orleans Versionen ausführen.

Solche bahnbrechenden Änderungen wurden seit vielen Jahren vermieden, auch über Hauptversionen hinweg. Warum jetzt? Es gibt zwei Hauptgründe: Identitäten und Serialisierung. In Bezug auf Identitäten bestehen Korn- und Stream-Identitäten jetzt aus Zeichenfolgen. Dadurch können Getreide generische Typinformationen ordnungsgemäß codieren und das Zuordnen von Datenströmen zur Anwendungsdomäne vereinfachen. Zuvor identifizierte Orleans Korntypen mithilfe einer komplexen Datenstruktur, die keine generischen Getreidearten darstellen konnte, was zu Spezialfällen führte. Datenströme wurden durch einen string Namespace und einen Guid Schlüssel identifiziert, der effizient, aber schwierig zur Anwendungsdomäne zuzuordnen war. Serialisierung ist jetzt versionstolerant. Dies bedeutet, dass Typen auf bestimmte kompatible Weise geändert werden können, nach einer Reihe von Regeln, mit dem Vertrauen, dass die Anwendung ohne Serialisierungsfehler aktualisiert werden kann. Diese Funktion ist besonders hilfreich, wenn Anwendungstypen in Datenströmen oder Grainspeichern beibehalten werden. In den folgenden Abschnitten werden die wesentlichen Änderungen beschrieben und näher diskutiert.

Änderungen beim Packen

Führen Sie beim Aktualisieren eines Projekts auf Orleans 7.0 die folgenden Aktionen aus:

  • Alle Clients müssen auf Microsoft.Orleans.Client verweisen.
  • Alle Silos (Server) müssen auf Microsoft.Orleans.Server verweisen.
  • Alle anderen Pakete müssen auf Microsoft.Orleans.Sdk verweisen.
  • Entfernen Sie alle Verweise auf Microsoft.Orleans.CodeGenerator.MSBuild und Microsoft.Orleans.OrleansCodeGenerator.Build.
  • Entfernen Sie alle Verweise auf Microsoft.Orleans.OrleansRuntime.
  • Entfernen Sie Aufrufe von ConfigureApplicationParts. Anwendungsteile wurden entfernt. Der C#-Quellgenerator für Orleans wird allen Paketen (einschließlich Client und Server) hinzugefügt und generiert automatisch das Äquivalent von Anwendungsteilen.
  • Ersetzen Sie Verweise auf Microsoft.Orleans.OrleansServiceBus durch Microsoft.Orleans. Streaming.EventHubs.
  • Wenn Sie Erinnerungen verwenden, fügen Sie einen Verweis auf Orleans hinzu.
  • Wenn Sie Streams verwenden, fügen Sie einen Verweis auf Orleans hinzu.

Tipp

Für alle Orleans-Beispiele ist ein Upgrade auf Orleans 7.0 erfolgt. Sie können sie als Referenz für die vorgenommenen Änderungen verwenden. Weitere Informationen finden Sie unter Orleans-Issue 8035, in dem die Änderungen aufgeführt sind, die an den einzelnen Beispielen vorgenommen wurden.

Orleans global mithilfe von Direktiven

Alle Orleans-Projekte verweisen entweder direkt oder indirekt auf das NuGet-Paket Microsoft.Orleans.Sdk. Wenn ein Orleans Projekt so konfiguriert ist, dass implizite Verwendungen (z. B. <ImplicitUsings>enable</ImplicitUsings>) aktiviert sind, verwendet das Projekt implizit sowohl die Orleans- als auch die Orleans.Hosting-Namespaces. Dies bedeutet, dass App-Code diese using Direktiven nicht benötigt.

Weitere Informationen finden Sie unter ImplicitUsings und dotnet/orleans/src/Orleans.Sdk/build/Microsoft.Orleans.Sdk.targets.

Gastgeberrolle

Der ClientBuilder Typ wird durch die UseOrleansClient Erweiterungsmethode auf IHostBuilder ersetzt. Der Typ IHostBuilder stammt aus dem NuGet-Paket Microsoft.Extensions.Hosting. Dies bedeutet, dass ein Orleans Client einem vorhandenen Host hinzugefügt werden kann, ohne einen separaten Container zum Einfügen von Abhängigkeiten zu erstellen. Der Client stellt während des Startvorgangs eine Verbindung mit dem Cluster her. Sobald IHost.StartAsync abgeschlossen ist, stellt der Client die Verbindung automatisch her. Dienste werden dem IHostBuilder Start in der Reihenfolge der Registrierung hinzugefügt. Das Aufrufen von UseOrleansClient vor ConfigureWebHostDefaults stellt z. B. sicher, dass Orleans gestartet wird, bevor ASP.NET Core startet, wodurch sofortiger Zugriff auf den Client über die ASP.NET Core-Anwendung möglich ist.

Um das vorherige ClientBuilder Verhalten zu emulieren, erstellen Sie eine separate HostBuilder und konfigurieren sie mit einem Orleans Client. Eine IHostBuilder kann entweder mit einem Orleans Client oder mit einem Orleans Silo konfiguriert werden. Alle Silos registrieren eine Instanz von IGrainFactory und IClusterClient, die die Anwendung verwenden kann, sodass das Konfigurieren eines Clients separat überflüssig und nicht unterstützt ist.

Änderungen der Signaturen OnActivateAsync und OnDeactivateAsync

Orleans ermöglicht Grains während der Aktivierung und Deaktivierung die Ausführung von Code. Verwenden Sie diese Funktion, um Aufgaben auszuführen, z. B. das Lesen des Zustands aus Speicher- oder Protokollierungslebenszyklusmeldungen. In Orleans 7.0 hat sich die Signatur dieser Lebenszyklusmethoden geändert:

  • OnActivateAsync() akzeptiert jetzt den Parameter CancellationToken. Wenn der CancellationToken Vorgang abgebrochen wird, beenden Sie den Aktivierungsprozess.
  • OnDeactivateAsync() akzeptiert jetzt die Parameter DeactivationReason und CancellationToken. DeactivationReason gibt an, warum die Aktivierung deaktiviert wurde. Verwenden Sie diese Informationen für Protokollierungs- und Diagnosezwecke. Wenn der CancellationToken Vorgang abgebrochen wird, schließen Sie den Deaktivierungsprozess umgehend ab. Beachten Sie, dass es nicht empfohlen wird, sich auf OnDeactivateAsync zu verlassen, um wichtige Aktionen auszuführen, wie z. B. das Beibehalten eines kritischen Zustands, da jeder Host jederzeit fehlschlagen kann.

Betrachten Sie das folgende Beispiel eines Grains, das diese neuen Methoden überschreibt:

public sealed class PingGrain : Grain, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(ILogger<PingGrain> logger) =>
        _logger = logger;

    public override Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public override Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

POCO-Körner und IGrainBase

Grains in Orleans müssen nicht mehr von der Basisklasse Grain oder einer anderen Klasse erben. Diese Funktionalität wird als POCO-Grains bezeichnet. So greifen Sie auf eine beliebige der folgenden Erweiterungsmethoden zu:

Ein Grain muss entweder IGrainBase implementieren oder von Grain erben. Hier ist ein Beispiel für die Implementierung IGrainBase einer Getreideklasse:

public sealed class PingGrain : IGrainBase, IPingGrain
{
    public PingGrain(IGrainContext context) => GrainContext = context;

    public IGrainContext GrainContext { get; }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

IGrainBase definiert OnActivateAsync und OnDeactivateAsync mit Standardimplementierungen, wodurch der Grain bei Wunsch an seinem Lebenszyklus teilnehmen kann:

public sealed class PingGrain : IGrainBase, IPingGrain
{
    private readonly ILogger<PingGrain> _logger;

    public PingGrain(IGrainContext context, ILogger<PingGrain> logger)
    {
        _logger = logger;
        GrainContext = context;
    }

    public IGrainContext GrainContext { get; }

    public Task OnActivateAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("OnActivateAsync()");
        return Task.CompletedTask;
    }

    public Task OnDeactivateAsync(DeactivationReason reason, CancellationToken token)
    {
        _logger.LogInformation("OnDeactivateAsync({Reason})", reason);
        return Task.CompletedTask;
    }

    public ValueTask Ping() => ValueTask.CompletedTask;
}

Serialisierung

Die aufwändigste Änderung in Orleans 7.0 ist die Einführung des versionstoleranten Serialisierungsmoduls. Diese Änderung wurde vorgenommen, da Anwendungen tendenziell weiterentwickelt werden, was eine erhebliche Herausforderung für Entwickler darstellte, da der vorherige Serialisierer das Hinzufügen von Eigenschaften zu bestehenden Typen nicht erlaubte. Andererseits war der vorherige Serialisierer flexibel und ermöglichte die Darstellung der meisten .NET-Typen ohne Änderung, einschließlich Features wie Generika, Polymorphismus und Referenznachverfolgung. Ein Ersatz war lange überfällig, aber eine qualitativ hochwertige Darstellung von Typen ist noch erforderlich. Daher führt 7.0 einen Ersatz-Serialisierer ein, Orleans der die Darstellung von .NET-Typen mit hoher Genauigkeit unterstützt und gleichzeitig die Entwicklung von Typen ermöglicht. Der neue Serialisierer ist wesentlich leistungsfähiger als der vorherige und führt zu einer bis zu 170% höheren End-to-End-Durchsatzsteigerung.

Weitere Informationen finden Sie in den folgenden Artikeln zu Orleans 7.0:

Identitäten von Grains

Körner haben jeweils eine einzigartige Identität, die aus dem Getreidetyp und seinem Schlüssel besteht. In früheren Orleans Versionen wurde ein Zusammengesetzter Typ für GrainIds verwendet, um Kornschlüssel von einer der folgenden Zuständen zu unterstützen:

Dieser Ansatz erfordert eine gewisse Komplexität beim Umgang mit Kornschlüsseln. Identitäten von Grains bestehen aus zwei Komponenten: Typ und Schlüssel. Die Typkomponente bestand zuvor aus einem numerischen Typcode, einer Kategorie und drei Bytes mit generischen Typinformationen.

Kornidentitäten nehmen jetzt die Form type/keyan, wobei beide type und key Zeichenfolgen sind. Die am häufigsten verwendete Kornschlüsselschnittstelle ist IGrainWithStringKey. Sie vereinfacht die Funktionsweise der Grainidentität erheblich und verbessert die Unterstützung generischer Graintypen.

Kornschnittstellen werden jetzt auch durch einen menschlich lesbaren Namen dargestellt, anstatt durch eine Kombination aus einem Hashcode und einer Zeichenfolgendarstellung jeglicher generischen Typparameter.

Das neue System ist anpassbarer, und diese Anpassungen können mit Attributen gesteuert werden.

  • GrainTypeAttribute(String) auf einem Korn class gibt den Typteil seiner Korn-ID an.
  • DefaultGrainTypeAttribute(String)bei einem Korn spezifiziert den interface, der standardmäßig aufgelöst werden soll, wenn ein Kornverweis abgerufen wird. Wenn beispielsweise aufgerufen wird IGrainFactory.GetGrain<IMyGrain>("my-key"), gibt die Getreidefabrik einen Verweis auf das Korn "my-type/my-key" zurück, wenn IMyGrain das oben genannte Attribut angegeben ist.
  • GrainInterfaceTypeAttribute(String) ermöglicht das Überschreiben des Schnittstellennamens. Wenn Sie einen Namen explizit mithilfe dieses Mechanismus angeben, können Sie den Schnittstellentyp umbenennen, ohne die Kompatibilität mit vorhandenen Kornbezügen zu unterbrechen. Beachten Sie, dass die Schnittstelle in diesem Fall auch das AliasAttribute aufweisen sollte, da ihre Identität möglicherweise serialisiert wird. Weitere Informationen zum Angeben eines Typalias finden Sie im Abschnitt zur Serialisierung.

Wie bereits erwähnt, ermöglicht das Überschreiben der Standardkornklasse und der Schnittstellennamen für Typen das Umbenennen der zugrunde liegenden Typen, ohne die Kompatibilität mit vorhandenen Bereitstellungen zu unterbrechen.

Streamidentitäten

Als Orleans-Streams erstmals veröffentlicht wurden, konnten Streams nur mithilfe von Guid identifiziert werden. Dieser Ansatz war in Bezug auf die Speicherzuweisung effizient, machte aber die Erstellung aussagekräftiger Datenstromidentitäten schwierig, wobei häufig eine Codierung oder Dereferenzierung erforderlich war, um die entsprechende Datenstromidentität für einen bestimmten Zweck zu ermitteln.

In Orleans 7.0 werden Datenströme mithilfe von Zeichenfolgen identifiziert. Dies Orleans.Runtime.StreamIdstruct enthält drei Eigenschaften: StreamId.Namespace, , StreamId.Keyund StreamId.FullKey. Bei diesen Eigenschaftswerten handelt es sich um codierte UTF-8-Zeichenfolgen. Siehe zum Beispiel StreamId.Create(String, String).

Ersetzung von SimpleMessageStreams durch BroadcastChannel

SimpleMessageStreams (auch als SMS bezeichnet) wird in 7.0 entfernt. SMS hatte dieselbe Schnittstelle wie Orleans.Providers.Streams.PersistentStreams, aber sein Verhalten war sehr unterschiedlich, da es auf direkte Korn-zu-Korn-Aufrufe basierte. Um Verwirrung zu vermeiden, wurde SMS entfernt und ein neuer Ersatz namens eingeführt Orleans.BroadcastChannel .

BroadcastChannel unterstützt nur implizite Abonnements und kann in diesem Fall ein direkter Ersatz sein. Wenn explizite Abonnements erforderlich sind oder die PersistentStream-Schnittstelle verwendet werden muss (z. B. wenn SMS für Tests und EventHub in der Produktion verwendet wurde), ist MemoryStream der beste Kandidat.

BroadcastChannel weist das gleiche Verhalten wie SMS auf, verhält sich jedoch MemoryStream wie andere Datenstromanbieter. Betrachten Sie das folgende Beispiel für die Verwendung von BroadcastChannel:

// Configuration
builder.AddBroadcastChannel(
    "my-provider",
    options => options.FireAndForgetDelivery = false);

// Publishing
var grainKey = Guid.NewGuid().ToString("N");
var channelId = ChannelId.Create("some-namespace", grainKey);
var stream = provider.GetChannelWriter<int>(channelId);

await stream.Publish(1);
await stream.Publish(2);
await stream.Publish(3);

// Simple implicit subscriber example
[ImplicitChannelSubscription]
public sealed class SimpleSubscriberGrain : Grain, ISubscriberGrain, IOnBroadcastChannelSubscribed
{
    // Called when a subscription is added to the grain
    public Task OnSubscribed(IBroadcastChannelSubscription streamSubscription)
    {
        streamSubscription.Attach<int>(
          item => OnPublished(streamSubscription.ChannelId, item),
          ex => OnError(streamSubscription.ChannelId, ex));

        return Task.CompletedTask;

        // Called when an item is published to the channel
        static Task OnPublished(ChannelId id, int item)
        {
            // Do something
            return Task.CompletedTask;
        }

        // Called when an error occurs
        static Task OnError(ChannelId id, Exception ex)
        {
            // Do something
            return Task.CompletedTask;
        }
    }
}

Die Migration zu MemoryStream ist einfacher, da nur die Konfiguration geändert werden muss. Beachten Sie die folgende Konfiguration von MemoryStream:

builder.AddMemoryStreams<DefaultMemoryMessageBodySerializer>(
    "in-mem-provider",
    _ =>
    {
        // Number of pulling agent to start.
        // DO NOT CHANGE this value once deployed, if you do rolling deployment
        _.ConfigurePartitioning(partitionCount: 8);
    });

OpenTelemetry

Das Telemetriesystem wird in Orleans 7.0 aktualisiert, und das vorherige System wird zugunsten standardisierter .NET-APIs wie .NET-Metriken für Metriken und ActivitySource für die Ablaufverfolgung entfernt.

In diesem Rahmen werden die vorhandenen Microsoft.Orleans.TelemetryConsumers.* Pakete entfernt. Eine neue Gruppe von Paketen wird berücksichtigt, um die Integration von Metriken zu optimieren, die von Orleans der Überwachungslösung der Wahl ausgegeben werden. Wie immer sind Feedback und Beiträge willkommen.

Das Tool dotnet-counters bietet eine Leistungsüberwachung zur Ad-hoc-Überwachung der Integrität und zur Leistungsuntersuchung auf erster Ebene. Verwenden Sie für Orleans Zähler das Tool dotnet-counters, um sie zu überwachen.

dotnet counters monitor -n MyApp --counters Microsoft.Orleans

Fügen Sie in ähnlicher Weise die Microsoft.Orleans Meter zu OpenTelemetry-Metriken hinzu, wie im folgenden Code gezeigt:

builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddPrometheusExporter()
        .AddMeter("Microsoft.Orleans"));

Um die verteilte Ablaufverfolgung zu aktivieren, konfigurieren Sie OpenTelemetry wie im folgenden Code dargestellt:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        tracing.SetResourceBuilder(ResourceBuilder.CreateDefault()
            .AddService(serviceName: "ExampleService", serviceVersion: "1.0"));

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.Orleans.Runtime");
        tracing.AddSource("Microsoft.Orleans.Application");

        tracing.AddZipkinExporter(options =>
        {
            options.Endpoint = new Uri("http://localhost:9411/api/v2/spans");
        });
    });

Im vorherigen Code ist OpenTelemetry für die Überwachung der folgenden Elemente konfiguriert:

  • Microsoft.Orleans.Runtime
  • Microsoft.Orleans.Application

Rufen Sie zum Weitergeben der Aktivität AddActivityPropagation auf:

builder.Host.UseOrleans((_, clientBuilder) =>
{
    clientBuilder.AddActivityPropagation();
});

Umgestalten von Features aus dem Kernpaket in separate Pakete

In Orleans 7.0 wurden Erweiterungen in separate Pakete ausgelagert, die nicht auf Orleans.Core angewiesen sind. Nämlich, Orleans.Streaming, Orleans.Remindersund Orleans.Transactions wurden vom Kern getrennt. Das bedeutet, dass diese Pakete vollständig für das, was verwendet wird, zahlen, und im Orleans Kern ist kein Code spezifisch für diese Features vorgesehen. Durch diesen Ansatz wird die Kernoberfläche und die Assemblygröße der API verringert, der Kern vereinfacht und die Leistung verbessert. In Bezug auf die Leistung erforderte es bei Transaktionen in Orleans zuvor die Ausführung von Code für jede Methode, um potenzielle Transaktionen zu koordinieren. Diese Koordinationslogik wird nun pro Methode verschoben.

Dies ist ein Breaking Change bei der Kompilierung. Bestehender Code, der mit Erinnerungen oder Datenströmen interagiert, indem Methoden aufgerufen werden, die zuvor für die Grain Basisklasse definiert wurden, könnte fehlschlagen, da es sich bei diesen nun um Erweiterungsmethoden handelt. Aktualisieren Sie Aufrufe, die nicht this angeben (z. B. GetReminders), um this einzuschließen (z. B. this.GetReminders()), da Erweiterungsmethoden qualifiziert sein müssen. Ein Kompilierungsfehler tritt auf, wenn diese Aufrufe nicht aktualisiert werden, und die erforderliche Codeänderung ist möglicherweise nicht offensichtlich, ohne zu wissen, was geändert wurde.

Transaktionsclient

Orleans 7.0 führt eine neue Abstraktion für die Koordinierung von Transaktionen ein: Orleans.ITransactionClient. Bisher konnte nur Getreide Transaktionen koordinieren. Mit ITransactionClient, das über Abhängigkeitsinjektion verfügbar ist, können Klienten Transaktionen auch ohne eine Zwischenschicht koordinieren. Im folgenden Beispiel wird innerhalb einer einzigen Transaktion Guthaben von einem Konto abgehoben und auf ein anderes eingezahlt. Rufen Sie diesen Code innerhalb eines Korns oder von einem externen Client auf, der ITransactionClient aus dem Dependency-Injection-Container abgerufen hat.

await transactionClient.RunTransaction(
  TransactionOption.Create,
  () => Task.WhenAll(from.Withdraw(100), to.Deposit(100)));

Für vom Client koordinierte Transaktionen muss der Client während der Konfiguration die erforderlichen Dienste hinzufügen:

clientBuilder.UseTransactions();

Das Beispiel BankAccount veranschaulicht die Verwendung von ITransactionClient. Weitere Informationen finden Sie unter Orleans-Transaktionen.

Eintrittsinvarianz der Aufrufkette

Grains sind standardmäßig Singlethread und verarbeiten die Anforderungen nacheinander von Anfang bis Ende. Mit anderen Worten: Grains sind standardmäßig eintrittsinvariant. Durch Das Hinzufügen der ReentrantAttribute Kornklasse kann das Korn mehrere Anforderungen gleichzeitig in interleavierender Weise verarbeiten und gleichzeitig singlethreaded sein. Diese Funktion kann nützlich sein, wenn Getreide keinen internen Zustand enthält oder viele asynchrone Vorgänge ausführt, z. B. das Ausgeben von HTTP-Aufrufen oder das Schreiben in eine Datenbank. Zusätzliche Sorgfalt ist erforderlich, wenn Anforderungen sich überschneiden können: Es ist möglich, dass der Zustand eines Korns, der vor einer await Anweisung beobachtet wird, sich ändert, bis der asynchrone Vorgang abgeschlossen ist und die Methode die Ausführung fortsetzt.

Das folgende Grain stellt beispielsweise einen Zähler dar. Sie ist markiert Reentrant, wodurch mehrere Anrufe verschachtelt werden können. Die Increment()-Methode sollte den internen Zähler erhöhen und den beobachteten Wert zurückgeben. Da der Increment() Methodenkörper jedoch den Zustand des Grain vor einem await Punkt beobachtet und anschließend aktualisiert, können mehrere verflochtene Ausführungen von Increment() zu einem _value führen, der weniger ist als die Gesamtanzahl der erhaltenen Increment() Aufrufe. Dies ist ein Fehler, der durch unsachgemäße Verwendung von Eintrittsinvarianz verursacht wurde.

Das Entfernen von ReentrantAttribute reicht aus, um dieses Problem zu beheben.

[Reentrant]
public sealed class CounterGrain : Grain, ICounterGrain
{
    int _value;

    /// <summary>
    /// Increments the grain's value and returns the previous value.
    /// </summary>
    public Task<int> Increment()
    {
        // Do not copy this code, it contains an error.
        var currentVal = _value;
        await Task.Delay(TimeSpan.FromMilliseconds(1_000));
        _value = currentVal + 1;
        return currentValue;
    }
}

Um solche Fehler zu verhindern, sind Grains standardmäßig nicht eintrittsinvariant. Der Nachteil ist ein reduzierter Durchsatz für Grains, die asynchrone Operationen in ihrer Implementierung ausführen, da das Grain andere Anfragen nicht bearbeiten kann, während auf den Abschluss einer asynchronen Operation gewartet wird. Um dieses Problem zu lösen, bietet Orleans mehrere Optionen, um in bestimmten Fällen Eintrittsinvarianz zu ermöglichen:

  • Für eine ganze Klasse: Durch das Platzieren des ReentrantAttribute Kerns kann jede Anfrage an den Kern mit jeder anderen Anfrage überlappen.
  • Für eine Teilmenge von Methoden: Das Platzieren der AlwaysInterleaveAttribute auf der Schnittstelle erlaubt es, dass Anfragen an diese Methode mit allen anderen Anfragen verflochten werden können, und erlaubt es auch allen anderen Anfragen, mit Anfragen an diese Methode verflochten zu werden.
  • Für eine Teilmenge von Methoden: Das Platzieren der ReadOnlyAttribute Methode auf der Kornschnittstelle erlaubt, dass Anforderungen an diese Methode sich mit jeder anderen ReadOnly Anforderung verweben und dass jede andere ReadOnly Anforderung sich mit Anforderungen an diese Methode verweben kann. In diesem Sinne ist es eine eingeschränktere Form von AlwaysInterleave.
  • Für jede Anforderung in einer Anrufkette: RequestContext.AllowCallChainReentrancy() und RequestContext.SuppressCallChainReentrancy() ermöglichen das Ein- und Ausschalten der Erlaubnis, dass nachgelagerte Anforderungen wieder in das System eingreifen können. Beide Aufrufe geben einen Wert zurück, der beim Verlassen der Anforderung verworfen werden muss. Verwenden Sie sie daher wie folgt:
public Task<int> OuterCall(IMyGrain other)
{
    // Allow call-chain reentrancy for this grain, for the duration of the method.
    using var _ = RequestContext.AllowCallChainReentrancy();
    await other.CallMeBack(this.AsReference<IMyGrain>());
}

public Task CallMeBack(IMyGrain grain)
{
    // Because OuterCall allowed reentrancy back into that grain, this method
    // will be able to call grain.InnerCall() without deadlocking.
    await grain.InnerCall();
}

public Task InnerCall() => Task.CompletedTask;

Opt-in für Reentranz von Aufrufketten pro Körnchen, pro Aufrufkette. Betrachten Sie z. B. zwei Körner, A und B. Wenn Korn A die Reentranz der Anrufkette vor dem Aufrufen von Korn B ermöglicht, kann Korn B in diesen Anruf wieder in Korn A zurückrufen. Korn A kann jedoch nicht wieder in Korn B zurückrufen, wenn Korn B nicht auch die Reentranz der Anrufkette aktiviert hat. Es ist pro Korn, pro Call-Chain aktiviert.

Mit using var _ = RequestContext.SuppressCallChainReentrancy() können Grains auch unterdrücken, dass Informationen über Eintrittsinvarianz der Aufrufkette nach unten gelangen. Dadurch wird verhindert, dass nachfolgende Aufrufe erneut eintreten.

Migrationsskripts für ADO.NET

Um die Vorwärtskompatibilität mit Orleans Clustering, Persistenz und Erinnerungen zu gewährleisten, die auf ADO.NET basieren, ist das entsprechende SQL-Migrationsskript erforderlich:

Wählen Sie die Dateien für die verwendete Datenbank aus, und wenden Sie sie der Reihe nach an.