在Orleans中,兩種排程形式與顆粒相關:
- 請求排程:根據 請求排程中所討論的規則,排定傳入的穀物請求以執行。
- 工作排程:以 單個線程 方式排程要執行的同步程式代碼區塊。
所有粒紋程式代碼都會在粒紋的工作排程器上執行,這表示要求也會在粒紋的工作排程器上執行。 即使要求排程規則允許多個要求同時執行,它們也不會平行執行,因為Grain的工作排程器一律會逐一執行工作,而且永遠不會平行執行多個工作。
工作排程
若要進一步瞭解排程,請考慮下列細節 MyGrain
。 它有一個稱為 DelayExecution()
的方法,會記錄訊息、等候一段時間,然後在傳回之前記錄另一則訊息。
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");
}
}
當這個方法執行時,方法主體會在兩個部分執行:
- 第一次
_logger.LogInformation(...)
呼叫以及對Task.Delay(1_000)
的呼叫。 - 第二個
_logger.LogInformation(...)
呼叫。
在Task.Delay(1_000)
呼叫完成之前,第二個工作不會在Grain的工作排程器上排程。 此時,它會排程粒紋方法的 接續 。
以下是如何排程並執行要求作為兩個工作的圖形表示法:
上述描述並非專屬於 Orleans;它描述工作排程在 .NET 中的運作方式。 C# 編譯器會將異步方法轉換成異步狀態機,並以離散的步驟運行此狀態機。 每個步驟都會在目前的TaskScheduler上排程(透過TaskScheduler.Current存取,預設為TaskScheduler.Default)或在目前的SynchronizationContext上。 如果使用TaskScheduler
,方法中的每個步驟都會成為傳遞給該Task
的TaskScheduler
實例。 因此,在 .NET 中,Task
可以代表兩個概念:
- 可以等候的異步操作。 上述方法的執行
DelayExecution()
是由一個可以等候的Task
表示物來表示。 - 同步工作區段。 上述方法中的每個
DelayExecution()
階段都會以Task
表示。
使用 TaskScheduler.Default
時,繼續會直接排程到 .NET ThreadPool,而且不會包裝在 Task
物件中。 在 Task
實例中的延續包裝會以透明方式發生,因此開發人員很少需要注意這些實作細節。
中的工作排程 Orleans
每個 grain 啟用都有自己的 TaskScheduler
實例,負責強制執行 grain 的 單線程 執行模型。 在內部,這個TaskScheduler
是通過ActivationTaskScheduler
和WorkItemGroup
實作。
WorkItemGroup
會將佇列中的工作保留在 Queue<T> 裡,其中 T
作為內部的 Task
,並實作 IThreadPoolWorkItem。 若要執行每個目前佇列中的Task
,WorkItemGroup
會在 .NET 上排程ThreadPool
。 當 .NET ThreadPool
呼叫 WorkItemGroup
的 IThreadPoolWorkItem.Execute()
方法時,WorkItemGroup
會逐一執行加入佇列的 Task
實例。
每個粒紋都有一個排程器,可藉由在 .NET ThreadPool
上排程本身來執行:
每個排程器都包含一個工作佇列:
.NET ThreadPool
會執行每個佇列中的工作項目。 這包括 顆粒調度器以及透過Task.Run(...)
排程的其他工作項目:
備註
Grain 的排程器一次只能在一個執行緒上執行,但它不一定會在相同的執行緒上運行。 每次執行粒紋排程器時,.NET ThreadPool
可以自由使用不同的線程。 粒紋的排程器可確保它一次只在一個線程上執行,並實作粒紋的單 一線程 執行模型。