Planungsübersicht
Es gibt zwei Formen der Planung in Orleans, die für Grains relevant sind:
- Anforderungsplanung: die Planung eingehender Grain-Aufrufe für die Ausführung gemäß den unter Anforderungsplanung erläuterten Planungsregeln.
- 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:
- Der erste
_logger.LogInformation(...)
-Aufruf und der Aufruf vonTask.Delay(1_000)
- 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:
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:
- Einen asynchronen Vorgang, auf den gewartet werden kann. Die Ausführung der obigen
DelayExecution()
-Methode wird durch einenTask
dargestellt, auf den gewartet werden kann. - In einem synchronen Arbeitsblock wird jede Phase innerhalb der obigen
DelayExecution()
-Methode durch einenTask
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 WorkItemGroup
sich 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:
Jeder Planer umfasst eine Warteschlange mit 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):
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.