Share via


Planungsübersicht

Es gibt zwei Formen der Planung in Orleans, die für Grains relevant sind:

  1. Anforderungsplanung: die Planung eingehender Grain-Aufrufe für die Ausführung gemäß den unter Anforderungsplanung erläuterten Planungsregeln.
  2. Taskplanung: Dies entspricht der Planung synchroner Codeblöcke, die in einzelnen Threads ausgeführt werden sollen.

Der gesamte Graincode wird im Taskplaner des Grains ausgeführt, was bedeutet, dass Anforderungen auch im Taskplaner des Grains erfolgen. Auch wenn die Anforderungsplanungsregeln die gleichzeitige Ausführung mehrerer Anforderungen zulassen, werden sie nicht parallel ausgeführt, da der Taskplaner des Grains Vorgänge immer nacheinander und niemals parallel ausführt.

Aufgabenplanung

Um die Planung besser nachvollziehen zu können, sehen Sie sich das folgende Grain (MyGrain) an. Es enthält eine Methode namens DelayExecution(), die eine Nachricht protokolliert, einige Zeit wartet und dann eine andere Nachricht protokolliert, bevor sie zurückgegeben wird.

public interface IMyGrain : IGrain
{
    Task DelayExecution();
}

public class MyGrain : Grain, IMyGrain
{
    private readonly ILogger<MyGrain> _logger;

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

    public async Task DelayExecution()
    {
        _logger.LogInformation("Executing first task");

        await Task.Delay(1_000);

        _logger.LogInformation("Executing second task");
    }
}

Wenn diese Methode ausgeführt wird, wird der Methodenkörper in zwei Teilen ausgeführt:

  1. Der erste _logger.LogInformation(...)-Aufruf und der Aufruf von Task.Delay(1_000)
  2. Der zweite _logger.LogInformation(...)-Aufruf

Der zweite Task wird im Taskplaner des Grains erst dann geplant, wenn der Task.Delay(1_000)-Aufruf abgeschlossen ist. Zu diesem Zeitpunkt wird die Fortsetzung der Grainmethode geplant.

Hier finden Sie eine grafische Darstellung, wie eine Anforderung geplant und in Form von zwei Tasks ausgeführt wird:

Two-Task-based request execution example.

Die obige Beschreibung ist nicht spezifisch für Orleans und erläutert die Taskplanung in .NET: Asynchrone Methoden in C# werden vom Compiler in einen asynchronen Zustandsautomat konvertiert, und die Ausführung über den asynchronen Zustandsautomat erfolgt in separaten Schritten. Jeder Schritt wird für den aktuellen TaskScheduler (Zugriff über TaskScheduler.Current, standardmäßig TaskScheduler.Default) oder den aktuellen SynchronizationContext geplant. Wenn ein TaskScheduler verwendet wird, wird jeder Schritt in der Methode durch eine Task-Instanz dargestellt, die an diesen TaskScheduler übergeben wird. Daher kann ein Task in .NET zwei konzeptionelle Elemente darstellen:

  1. Einen asynchronen Vorgang, auf den gewartet werden kann. Die Ausführung der obigen DelayExecution()-Methode wird durch einen Task dargestellt, auf den gewartet werden kann.
  2. In einem synchronen Arbeitsblock wird jede Phase innerhalb der obigen DelayExecution()-Methode durch einen Task dargestellt.

Bei Verwendung von TaskScheduler.Default werden Fortsetzungen direkt im .NET-ThreadPool geplant und nicht in ein Task-Objekt eingeschlossen. Das Einschließen von Fortsetzungen in Task-Instanzen erfolgt transparent, sodass Entwickler*innen diese Implementierungsdetails nur selten beachten müssen.

Taskplanung in Orleans

Jede Grainaktivierung umfasst eine eigene TaskScheduler-Instanz, die für das Erzwingen des Ausführungsmodells mit einzelnen Threads für Grains verantwortlich ist. Intern wird dieser TaskScheduler über ActivationTaskScheduler und WorkItemGroup implementiert. WorkItemGroup behält die in eine Warteschlange eingereihten Tasks in einer Queue<T>-Klasse bei, wobei T ein interner Task ist und IThreadPoolWorkItem implementiert. Zum Ausführen der aktuell in die Warteschlange eingereihten Task-Elemente plant WorkItemGroupsich selbst im .NET-ThreadPool. Wenn der .NET-ThreadPool die IThreadPoolWorkItem.Execute()-Methode von WorkItemGroup aufruft, führt die WorkItemGroup die in die Warteschlange eingereihten Task-Instanzen einzeln aus.

Jedes Grain verfügt über einen Planer, der ausgeführt wird, indem er sich selbst im .NET-ThreadPool plant:

Orleans grains scheduling themselves on the .NET ThreadPool.

Jeder Planer umfasst eine Warteschlange mit Tasks:

Scheduler queue of scheduled tasks.

Der .NET-ThreadPool führt jedes Arbeitselement aus, das sich in dessen Warteschlange befindet. Dazu gehören Grainplaner sowie andere Arbeitselemente (z. B. Arbeitselemente, die über Task.Run(...) geplant werden):

Visualization of the all schedulers running in the .NET ThreadPool.

Hinweis

Der Planer eines Grains kann nur jeweils für einen Thread ausgeführt werden, wird jedoch nicht immer für denselben Thread ausgeführt. Der .NET-ThreadPool kann bei jeder Ausführung des Grainplaners einen anderen Thread verwenden. Der Planer des Grains ist dafür verantwortlich, sicherzustellen, dass er nur für einen Thread ausgeführt wird. Auf diese Weise wird das Ausführungsmodell mit einzelnen Threads von Grains implementiert.