计划概述

在 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 是通过 ActivationTaskScheduler 和 WorkItemGroup 实现的。 WorkItemGroup 将排队的任务保存在 Queue<T> 中(其中 T 在内部是一个 Task),并实现 IThreadPoolWorkItem。 若要执行每个当前排队的 Task,WorkItemGroup 将在 .NET 上计划自身ThreadPool。 当 .NET ThreadPool 调用 WorkItemGroup 的 IThreadPoolWorkItem.Execute() 方法时,WorkItemGroup 会逐个执行排列的 Task 实例。

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

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

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

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

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

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

注释

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