计划概述

在 Orleans 中与粮食相关的两种调度形式:

  1. 请求调度:根据 请求调度中讨论的规则调度传入粮食请求的执行。
  2. 任务计划:计划以 单线程 方式执行的同步代码块。

所有粒度代码在粒度的任务计划程序上执行,这意味着请求也会在粒度的任务计划程序上执行。 即使请求调度规则允许多个请求并发执行,它们也不会并行执行,因为该任务调度程序始终逐个执行任务,并且永远不会并行执行多个任务。

任务计划程序

若要更好地了解日程安排,请考虑以下粒度 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");
    }
}

此方法执行时,方法正文分两部分执行:

  1. 第一个部分是 _logger.LogInformation(...) 调用以及对 Task.Delay(1_000) 的调用。
  2. 第二个部分是 _logger.LogInformation(...) 调用。

Task.Delay(1_000)调用完成之前,第二个任务不会在粒度任务计划程序中安排。 此时,它会安排谷粒方法的 延续

下面是一个图形表示,展示如何将一个请求计划并执行为两个任务。

基于两个任务的请求执行示例。

上述说明不特定于 Orleans;它描述任务计划在 .NET 中的工作原理。 C# 编译器将异步方法转换为异步状态机,并在离散步骤中通过此状态机执行。 每个步骤按当前 TaskScheduler (通过 TaskScheduler.Current默认访问 TaskScheduler.Default)或当前 SynchronizationContext步骤进行计划。 如果使用TaskScheduler,则该方法中的每个步骤都表示一个Task 实例,该实例被传递给TaskScheduler。 因此,.NET 中的 Task 可以表示两种概念:

  1. 可以等待的异步操作 上述DelayExecution()方法的执行由一个可等待的Task表示。
  2. 同步的工作单元。 上述方法中的每个DelayExecution()所处阶段均由一个Task来表示。

使用 TaskScheduler.Default 时,延续任务会直接安排到 .NET ThreadPool 上,并且不会被包装在 Task 对象中。 实例中的 Task 延续的包装是透明的,因此开发人员很少需要了解这些实现详细信息。

Orleans 中的任务计划

每个粒度激活都有自己的 TaskScheduler 实例,负责强制实施粒度的单 线程 执行模型。 在内部,此 TaskScheduler 是通过 ActivationTaskSchedulerWorkItemGroup 实现的。 WorkItemGroup 将排队的任务保存在 Queue<T> 中(其中 T 在内部是一个 Task),并实现 IThreadPoolWorkItem。 若要执行每个当前排队的 TaskWorkItemGroup 将在 .NET 上计划自身ThreadPool。 当 .NET ThreadPool 调用 WorkItemGroupIThreadPoolWorkItem.Execute() 方法时,WorkItemGroup 会逐个执行排列的 Task 实例。

每个任务都有一个调度器,该调度器通过在 .NET ThreadPool上进行自我调度来执行:

Orleans grain 在 .NET ThreadPool 上自行进行计划。

每个计划程序包含一个任务队列:

计划程序的已计划任务队列。

.NET ThreadPool 执行其中排队的每个工作项。 这包括 任务调度器 以及其他工作项,例如通过 Task.Run(...)

在 .NET ThreadPool 中运行的所有计划程序的可视化效果。

注释

一个粒子的调度器每次只能在一个线程上执行,但并不是总在同一个线程上执行。 每次执行 Grain 的调度程序时,.NET ThreadPool 都可以自由使用不同的线程。 粒子的调度程序确保它一次只在一个线程上执行,实现粒子的单线程执行模型。