计划概述
Orleans 中有两种与 grain 相关的计划形式:
- 请求计划:将传入的 grain 调用计划为根据请求计划中所述的计划规则执行。
- 任务计划:将同步代码块计划为以单线程方式执行
所有 grain 代码都在 grain 的任务计划程序中执行,这意味着,请求也在 grain 的任务计划程序中执行。 即使请求计划规则允许多个请求并发执行,这些请求也不会并行执行,因为 grain 的任务计划程序始终逐个执行任务,因此永远不会并行执行多个任务。
任务计划程序
为了更好地了解计划,请考虑以下 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 的任务计划程序中进行计划,此时它会计划 grain 方法的延续。
下面是如何计划请求并将其作为两个任务执行的图形表示形式:
以上说明并非特定于 Orleans,而是 .NET 中任务计划的工作原理:编译器将 C# 中的异步方法转换为异步状态机,执行通过异步状态机以离散步骤的形式不断进行。 每个步骤在当前的 TaskScheduler(通过 TaskScheduler.Current 访问,默认为 TaskScheduler.Default)或当前的 SynchronizationContext 中计划。 如果使用 TaskScheduler
,则方法中的每个步骤由传递给该 TaskScheduler
的 Task
实例表示。 因此,.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
实例。
每个 grain 都有一个计划程序,该计划程序通过在 .NET ThreadPool
上计划自身来执行:
每个计划程序包含一个任务队列:
.NET ThreadPool
执行其中排队的每个工作项。 这包括 grain 计划程序以及其他工作项,例如通过 Task.Run(...)
计划的工作项:
注意
grain 的计划程序每次只能在一个线程上执行,但它并不始终在同一个线程上执行。 每次执行 grain 的计划程序时,.NET ThreadPool
都可以任意使用不同的线程。 grain 的计划程序负责确保每次只在一个线程上执行,这也是 grain 的单线程执行模型的实现方式。