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

Bestehende Anwendungen, die Reminders, Streams oder Persistenz von Grains verwenden, können aufgrund von Änderungen in der Art und Weise, wie Orleans Grains und Streams identifiziert, nicht einfach zu Orleans 7.0 migriert werden. Wir planen, einen Migrationspfad für diese Anwendungen schrittweise anzubieten.

Anwendungen, die mit früheren Versionen von Orleans arbeiten, können nicht problemlos über ein paralleles Upgrade auf Orleans 7.0 aktualisiert werden. Daher muss eine andere Upgradestrategie verfolgt werden, z. B. die Bereitstellung eines neuen Clusters und Außerbetriebnahme des vorherigen Clusters. Orleans 7.0 ändert das Wire-Protokoll auf inkompatible Weise, d. h. Cluster können keine Kombination aus Orleans 7.0-Hosts und Hosts mit früheren Versionen von Orleans enthalten.

Wir haben solche Breaking Changes viele Jahre vermieden, sogar in allen Hauptversionen, warum jetzt? Es gibt zwei Hauptgründe: Identitäten und Serialisierung. In Bezug auf Identitäten bestehen Grain- und Streamidentitäten jetzt aus Zeichenfolgen, sodass Grains generische Typinformationen ordnungsgemäß codieren und Streams einfacher der Anwendungsdomäne zugeordnet werden können. Graintypen wurden zuvor anhand einer komplexen Datenstruktur identifiziert, die keine generischen Grains abbilden konnte, was zu Problemfällen führte. Streams wurden anhand des Namespace string und des Schlüssel Guid identifiziert, was es Entwicklern erschwerte, eine Zuordnung zu ihrer Anwendungsdomäne vorzunehmen, auch wenn dies effizient war. Die Serialisierung ist jetzt versionstolerant, d. h. Sie können Ihre Typen auf bestimmte kompatible Weise ändern, indem Sie eine Reihe von Regeln befolgen, und sicher sein, dass Sie Ihre Anwendung ohne Serialisierungsfehler aktualisieren können. Dies war besonders problematisch, wenn Anwendungstypen in Streams oder Grainspeicher dauerhaft gespeichert wurden. In den folgenden Abschnitten werden die wichtigsten Änderungen ausführlich beschrieben und erläutert.

Änderungen beim Packen

Wenn Sie für ein Projekt ein Upgrade auf Orleans 7.0 vornehmen, müssen Sie die folgenden Aktionen ausführen:

  • 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. Teile der Anwendung wurden entfernt. Der C#-Quellcodegenerator für Orleans wurde allen Paketen (einschließlich Client und Server) hinzugefügt und generiert automatisch das Äquivalent von Teilen der Anwendung.
  • Ersetzen Sie Verweise auf Microsoft.Orleans.OrleansServiceBus durch Microsoft.Orleans.Streaming.EventHubs.
  • Wenn Sie Reminders verwenden, fügen Sie einen Verweis auf Microsoft.Orleans.Reminders hinzu.
  • Wenn Sie Streams verwenden, fügen Sie einen Verweis auf Microsoft.Orleans.Streaming 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.

Orleansglobal using-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 Usings-Elemente (z. B. <ImplicitUsings>enable</ImplicitUsings>) aktiviert sind, werden die Namespaces Orleans und Orleans.Hosting beide implizit verwendet. Dies bedeutet, dass Ihr App-Code diese Anweisungen nicht benötigt.

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

Hosting

Der Typ ClientBuilder wurde durch die Erweiterungsmethode UseOrleansClient für IHostBuilder ersetzt. Der Typ IHostBuilder stammt aus dem NuGet-Paket Microsoft.Extensions.Hosting. Das bedeutet, dass Sie einen Orleans-Client einem vorhandenen Host hinzufügen können, ohne einen separaten Dependency Injection-Container erstellen zu müssen. Der Client stellt während des Startvorgangs eine Verbindung mit dem Cluster her. Nach Abschluss des Vorgangs IHost.StartAsync wird der Client automatisch verbunden. Zu IHostBuilder hinzugefügte Dienste werden in der Reihenfolge ihrer Registrierung gestartet. Wenn Sie also UseOrleansClient vor ConfigureWebHostDefaults aufrufen, wird Orleans beispielsweise vor dem Start von ASP.NET Core gestartet, sodass Sie in Ihrer ASP.NET Core-Anwendung sofort auf den Client zugreifen können.

Wenn Sie das vorherige ClientBuilder-Verhalten emulieren möchten, können Sie ein separates HostBuilder-Element erstellen und mit einem Orleans-Client konfigurieren. Für IHostBuilder kann entweder ein Orleans-Client oder Orleans-Silo konfiguriert sein. Alle Silos registrieren eine Instanz von IGrainFactory und IClusterClient, die die Anwendung verwenden kann, sodass das separate Konfigurieren eines Clients unnötig ist und nicht unterstützt wird.

Änderungen der Signaturen OnActivateAsync und OnDeactivateAsync

Orleans ermöglicht Grains während der Aktivierung und Deaktivierung die Ausführung von Code. Dies dient für Aufgaben wie das Lesen des Status aus dem Speicher oder das Protokollieren von Lebenszyklusnachrichten. In Orleans 7.0 hat sich die Signatur dieser Lebenszyklusmethoden geändert:

  • OnActivateAsync() akzeptiert jetzt den Parameter CancellationToken. Wenn CancellationToken abgebrochen wird, muss der Aktivierungsprozess abgebrochen werden.
  • OnDeactivateAsync() akzeptiert jetzt die Parameter DeactivationReason und CancellationToken. DeactivationReason gibt an, warum die Aktivierung deaktiviert wurde. Von Entwicklern wird erwartet, dass sie diese Informationen für Protokollierungs- und Diagnosezwecke verwenden. Wenn CancellationToken abgebrochen wird, muss der Deaktivierungsprozess umgehend abgeschlossen werden. Beachten Sie, dass Hosts jederzeit ausfallen können. Es ist daher nicht empfehlenswert, sich auf OnDeactivateAsync zu verlassen, wenn es um wichtige Aktionen wie die Aufrechterhaltung kritischer Zustände geht.

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-Grains 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:

Ihr Grain muss entweder IGrainBase implementieren oder von Grain erben. Hier sehen Sie ein Beispiel der Implementierung von IGrainBase für eine Grain-Klasse:

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

    public IGrainContext GrainContext { get; }

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

IGrainBase definiert außerdem OnActivateAsync und OnDeactivateAsync mit Standardimplementierungen, sodass Ihr Grain nach Wunsch am 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 ist erfolgt, weil Anwendungen sich häufig weiterentwickeln. Dies war ein erheblicher Stolperstein für Entwickler, da das vorherige Serialisierungsmodul das Hinzufügen von Eigenschaften zu vorhandenen Typen nicht tolerieren konnte. Andererseits war das Serialisierungsmodul flexibel und ermöglichte es Entwicklern, die meisten .NET-Typen ohne Änderungen abzubilden, einschließlich Features wie Generics, Polymorphismus und Nachverfolgung von Verweisen. Ein Ersatz war längst überfällig, aber Benutzer benötigen weiterhin ein originalgetreues Abbilden ihrer Typen. Daher wurde in Orleans 7.0 ein Ersatzserialisierungsmodul eingeführt, das das originalgetreue Abbilden von .NET-Typen unterstützt und gleichzeitig die Weiterentwicklung von Typen zulässt. Das neue Serialisierungsmodul ist wesentlich effizienter als das vorherige, was durchgängig zu einem bis zu 170 % höheren Durchsatz führt.

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

Identitäten von Grains

Grains haben jeweils eine eindeutige Identität, die aus dem Typ und Schlüssel des Grains besteht. In früheren Versionen von Orleans wurde ein zusammengesetzter Typ für GrainId-Elemente verwendet, um die folgenden Grainschlüssel zu unterstützen:

Dies ist mit einer gewissen Komplexität verbunden, wenn es um den Umgang mit Grainschlüsseln geht. 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.

Identitäten von Grains haben nun die Form type/key, wobei type und key Zeichenfolgen sind. Die am häufigsten verwendete Grainschlüsselschnittstelle ist IGrainWithStringKey. Sie vereinfacht die Funktionsweise der Grainidentität erheblich und verbessert die Unterstützung generischer Graintypen.

Grainschnittstellen werden jetzt auch durch einen von Menschen lesbaren Namen dargestellt und nicht mehr durch eine Kombination aus einem Hashcode und einer Zeichenfolge, die alle generischen Typparameter abbildet.

Das neue System ist besser anpassbar, und diese Anpassungen lassen sich durch Attribute steuern.

  • GrainTypeAttribute(String) für das Grain class gibt den Teil mit dem Typ der Grain-ID an.
  • DefaultGrainTypeAttribute(String) für das Grain interface gibt den Typ des Grains an, den IGrainFactory beim Abrufen eines Grainverweises standardmäßig auflösen soll. Wenn Sie z. B. IGrainFactory.GetGrain<IMyGrain>("my-key") aufrufen, gibt die Grainfactory einen Verweis auf das Grain "my-type/my-key" zurück, wenn für IMyGrain das oben genannte Attribut angegeben wurde.
  • GrainInterfaceTypeAttribute(String) ermöglicht das Überschreiben des Schnittstellennamens. Die explizite Angabe eines Namens mit diesem Mechanismus ermöglicht die Umbenennung des Schnittstellentyps, ohne Kompatibilität mit vorhandenen Verweisen auf Grains zu beeinträchtigen. Beachten Sie, dass Ihre Schnittstelle auch in diesem Fall AliasAttribute aufweisen sollte, da ihre Identität serialisiert werden kann. Weitere Informationen zum Angeben eines Typalias finden Sie im Abschnitt zur Serialisierung.

Wie bereits erwähnt, können Sie die Standardnamen der Grainklassen und -schnittstellen für Ihre Typen überschreiben und so die zugrunde liegenden Typen umbenennen, ohne die Kompatibilität mit vorhandenen Bereitstellungen zu beeinträchtigen.

Streamidentitäten

Als Orleans-Streams erstmals veröffentlicht wurden, konnten Streams nur mithilfe von Guid identifiziert werden. Dies war effizient, was die Arbeitsspeicherzuteilung betraf, aber es war für Benutzer schwierig, sinnvolle Streamidentitäten zu erstellen. Oftmals war eine Codierung oder Umleitung erforderlich, um die geeignete Streamidentität für einen bestimmten Zweck zu bestimmen.

In Orleans 7.0 werden Streams jetzt mithilfe von Zeichenfolgen identifiziert. Orleans.Runtime.StreamId struct enthält drei Eigenschaften: StreamId.Namespace, StreamId.Key und StreamId.FullKey. Bei diesen Eigenschaftswerten handelt es sich um codierte UTF-8-Zeichenfolgen. Beispiel: StreamId.Create(String, String).

Ersetzung von SimpleMessageStreams durch BroadcastChannel

SimpleMessageStreams (auch SMS genannt) wurde in Version 7.0 entfernt. SimpleMessageStreams verfügte über dieselbe Schnittstelle wie Orleans.Providers.Streams.PersistentStreams, verhielt sich aber ganz anders, da es auf direkte Aufrufe von Grain zu Grain angewiesen war. Um Verwirrung zu vermeiden, wurde SimpleMessageStreams entfernt und ein neuer Ersatz namens Orleans.BroadcastChannel eingeführt.

BroadcastChannel unterstützt nur implizite Abonnements und kann in diesem Fall ein direkter Ersatz sein. Wenn Sie explizite Abonnements benötigen oder die PersistentStream-Schnittstelle verwenden müssen (beispielsweise, wenn Sie SimpleMessageStreams in Tests verwendet haben, während EventHub in der Produktion eingesetzt wurde), dann ist MemoryStream die beste Wahl für Sie.

BroadcastChannel verhält sich genauso wie SimpleMessageStreams, während MemoryStream sich wie andere Streamanbieter verhält. 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 wurde in Orleans 7.0 aktualisiert und das vorherige System zugunsten standardisierter .NET-APIs wie .NET Metrics für Metriken und ActivitySource für Ablaufverfolgung entfernt.

In diesem Zusammenhang wurden die vorhandenen Microsoft.Orleans.TelemetryConsumers.*-Pakete entfernt. Wir erwägen die Einführung einer neuen Reihe von Paketen, um den Prozess der Integration der von Orleans ausgegebenen Metriken in die von Ihnen gewählte Überwachungslösung zu vereinfachen. 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. Für Orleans-Zähler kann das Tool dotnet-counters verwendet werden, um sie zu überwachen:

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

In ähnlicher Weise können OpenTelemetry-Metriken Microsoft.Orleans-Messungen hinzufügen, wie im folgenden Code gezeigt:

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

Um eine verteilte Ablaufverfolgung zu aktivieren, konfigurieren Sie OpenTelemetry wie im folgenden Code gezeigt:

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 haben wir uns bemüht, Erweiterungen in separate Pakete umzugestalten, die nicht auf Orleans.Core angewiesen sind. Genauer gesagt, wurden Orleans.Streaming, Orleans.Reminders und Orleans.Transactions vom Kern getrennt. Das bedeutet, dass Sie bei diesen Paketen ausschließlich für das zahlen, was Sie nutzen, und kein Code im Kern von Orleans für diese Features dediziert ist. Dadurch werden die API-Oberfläche des Kerns und die Größe der Assembly verschlankt, der Kern vereinfacht und die Leistung verbessert. Was die Leistung betrifft, so erforderten Transaktionen in Orleans bisher Code, der für jede Methode ausgeführt wurde, um mögliche Transaktionen zu koordinieren. Dies wurde inzwischen auf methodenbezogen umgestellt.

Dies ist ein Breaking Change bei der Kompilierung. Möglicherweise haben Sie vorhandenen Code, der mit Erinnerungen oder Streams interagiert, indem er Methoden aufruft, die zuvor in der Basisklasse Grain definiert waren, jetzt aber Erweiterungsmethoden sind. Solche Aufrufe, die nicht this angeben (z. B. GetReminders), müssen so geändert werden, dass sie this enthalten (z. B. this.GetReminders()), da Erweiterungsmethoden qualifiziert werden müssen. Wenn Sie diese Aufrufe nicht ändern, kommt es zu einem Kompilierungsfehler, und die erforderliche Codeänderung ist möglicherweise nicht offensichtlich, wenn Sie nicht wissen, was sich geändert hat.

Transaktionsclient

In Orleans 7.0 wurde mit Orleans.ITransactionClient eine neue Abstraktion zur Koordinierung von Transaktionen eingeführt. Bisher konnten Transaktionen nur durch Grains koordiniert werden. Mit ITransactionClient, das über Dependency Injection verfügbar ist, können Clients auch Transaktionen koordinieren, ohne dass ein zwischengeschaltetes Grain erforderlich ist. Im folgenden Beispiel wird innerhalb einer einzigen Transaktion Guthaben von einem Konto abgehoben und auf ein anderes eingezahlt. Dieser Code kann aus einem Grain heraus oder von einem externen Client aufgerufen werden, 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 die erforderlichen Dienste zur Konfigurationszeit 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 von ReentrantAttribute zu einer Grainklasse können mehrere Anforderungen gleichzeitig verarbeitet werden, und zwar auf geschachtelte Art und Weise, während sie immer noch Singlethread sind. Dies kann für Grains nützlich sein, die keinen internen Zustand enthalten oder viele asynchrone Vorgänge durchführen, wie z. B. HTTP-Aufrufe oder das Schreiben in eine Datenbank. Besondere Aufmerksamkeit ist geboten, wenn Anforderungen ineinander geschachtelt sein könnten. Es ist möglich, dass der Zustand eines Grains beobachtet wird, bevor eine await-Anweisung sich geändert hat, wenn der asynchrone Vorgang abgeschlossen ist und die Methode die Ausführung fortsetzt.

Das folgende Grain stellt beispielsweise einen Zähler dar. Es wurde mit Reentrant markiert, sodass mehrere Aufrufe ineinander geschachtelt sein dürfen. Die Increment()-Methode sollte den internen Zähler erhöhen und den beobachteten Wert zurückgeben. Da der Körper der Increment()-Methode jedoch den Status des Grains vor einem await-Punkt beobachtet und danach aktualisiert, ist es möglich, dass mehrere ineinander geschachtelte Ausführungen von Increment() zu einem _value kleiner als die Gesamtanzahl der empfangenen Aufrufe von Increment() führen können. Dies ist ein Fehler, der durch unsachgemäße Verwendung von Eintrittsinvarianz verursacht wurde.

Zum Beheben des Problems reicht das Entfernen von ReentrantAttribute.

[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 dabei ist ein verringerter Durchsatz für Grains, die in ihrer Implementierung asynchrone Vorgänge durchführen, da andere Anforderungen nicht verarbeitet werden können, während das Grain auf den Abschluss eines asynchronen Vorgangs wartet. Um dieses Problem zu lösen, bietet Orleans mehrere Optionen, um in bestimmten Fällen Eintrittsinvarianz zu ermöglichen:

  • Für eine gesamte Klasse: Wenn Sie ReentrantAttribute für das Grain angeben, kann jede Anforderung an das Grain mit jeder anderen Anforderung ineinander geschachtelt werden.
  • Für eine Teilmenge von Methoden: Wenn Sie die AlwaysInterleaveAttribute für die Schnittstellenmethode des Grains angeben, können Anforderungen an diese Methode mit jeder anderen Anforderung ineinander geschachtelt werden, und Anforderungen an diese Methode können von jeder anderen Anforderung ineinander geschachtelt werden.
  • Für eine Teilmenge von Methoden: Wenn Sie die ReadOnlyAttribute für die Schnittstellenmethode des Grains angeben, können Anforderungen an diese Methode mit jeder anderen Anforderung ineinander geschachtelt werden, und ReadOnly-Anforderungen an diese Methode können von jeder anderen ReadOnly-Anforderung ineinander geschachtelt werden. In diesem Sinne handelt es sich um eine eingeschränktere Form von AlwaysInterleave.
  • Für jede Anfrage innerhalb einer Aufrufkette: RequestContext.AllowCallChainReentrancy() und <xref:Orleans.Runtime.RequestContext.SuppressCallChainReentrancy?displayProperty=nameWithType ermöglicht, die Möglichkeit, dass nachgelagerte Anfragen wieder in das Grain zurückkehren, zuzulassen oder nicht zuzulassen. Die Aufrufe geben einen Wert zurück, der beim Beenden der Anforderung verworfen werden muss. Daher ist die ordnungsgemäße Verwendung 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;

Eintrittsinvarianz der Aufrufkette muss pro Grain und pro Aufrufkette aktiviert werden. Nehmen wir zum Beispiel zwei Grains: Grain A und Grain B. Wenn Grain A Eintrittsinvarianz der Aufrufkette aktiviert, ehe Grain B aufgerufen wird, kann Grain B bei diesem Aufruf einen Rückruf an Grain A tätigen. Grain A kann jedoch nicht in Grain B zurückgerufen werden, wenn für Grain B nicht auch Eintrittsinvarianz der Aufrufkette aktiviert ist. Dies muss pro Grain und pro Aufrufkette erfolgen.

Mit using var _ = RequestContext.SuppressCallChainReentrancy() können Grains auch unterdrücken, dass Informationen über Eintrittsinvarianz der Aufrufkette nach unten gelangen. Dies verhindert den Wiedereintritt nachfolgender Anrufe.

Migrationsskripts für ADO.NET

Um Aufwärtskompatibilität mit Orleans-Clustering, Persistenz und Reminders sicherzustellen, die auf ADO.NET basieren, benötigen Sie das entsprechende SQL-Migrationsskript:

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