2.Elsa源码探索-工作流引擎核心-WorkflowCore

一、这个模块是做什么的?

如果说 Elsa.Workflows.Management 是工作流的"档案室",那 Elsa.Workflows.Core 就是工作流的"发动机"。它负责把一个静态的工作流定义(WorkflowGraph)真正跑起来。

从启动第一个工作流节点、到循环执行每一步、到挂起等待外部信号、再到最终完成——整个动态执行过程都由WorkflowCore 模块负责。

二、核心数据结构

理解执行引擎之前我们要先看清楚它操作的几个核心对象。

  • WorkflowGraph:(Models/WorkflowGraph.cs,位于 Elsa.Workflows.Core)是工作流定义在内存中的形态,它包含三样东西:

    // Models/WorkflowGraph.cs
    public class WorkflowGraph(Workflow workflow, ActivityNode root, IEnumerable<ActivityNode> nodes)
    {
        public Workflow Workflow { get; } = workflow;
        public ActivityNode Root { get; } = root;
        public IReadOnlyList<ActivityNode> Nodes { get; } = nodes.ToList();
        public Dictionary<string, ActivityNode> NodeIdLookup { get; }      // 按 NodeId 查找
        public Dictionary<string, ActivityNode> NodeHashLookup { get; }    // 按 Hash 查找
        public Dictionary<IActivity, ActivityNode> NodeActivityLookup { get; } // 按活动实例反查
    }
    
    • Root:根节点(通常是 SequenceFlowchart
    • Nodes:所有活动节点的平铺列表
    • 三个查找字典:分别支持按 NodeId、按 Hash、按活动实例对象反查节点
  • ActivityNode:节点树上的每一个节点元数据

    通过 WorkflowGraph 的定义可以看到该类中大量使用了一个叫做ActivityNode的对象,该类是接整个图结构的最小调度单元。

    // Models/ActivityNode.cs
    public class ActivityNode
    {
        private readonly List<ActivityNode> _parents = new(); // 当前节点的父节点
        private readonly List<ActivityNode> _children = new(); // 当前节点的子节点
        private string? _nodeId; // 不同深度的同名活动有不同的 `NodeId`,保证全局唯一
        public string NodeId 
        {
            get
            {
                // NodeId 由祖先链构成,如 "rootId:parentId:childId"
                var ancestorIds = Ancestors().Reverse().Select(x => x.Activity.Id).ToList();
                _nodeId = ancestorIds.Any() ? $"{string.Join(":", ancestorIds)}:{Activity.Id}" : Activity.Id;
                return _nodeId;
            }
        }
    }
    
  • WorkflowExecutionContext:工作流上下文

    WorkflowExecutionContextContexts/WorkflowExecutionContext.cs)是整个执行过程中最重要的对象,可以理解为一个正在运行的工作流实例的完整内存快照:

    // Contexts/WorkflowExecutionContext.cs(构造函数节选)
    SubStatus = WorkflowSubStatus.Pending;
    Id = instanceId;                                      // 工作流实例 ID(string 类型)
    Scheduler = activitySchedulerFactory.CreateScheduler(); // 活动调度队列
    MemoryRegister = workflowGraph.Workflow.CreateRegister(); // 变量内存寄存器
    Input = input ?? new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
    

    它持有:当前调度队列(Scheduler)、所有活动执行上下文(ActivityExecutionContexts)、书签列表(Bookmarks)、变量内存寄存器(MemoryRegister)、执行日志、输入/输出、当前状态等。

  • ActivityExecutionContext:节点上下文

    ActivityExecutionContextContexts/ActivityExecutionContext.cs)是工作流执行上下文的"子集",代表某次具体的活动执行:

    // Contexts/ActivityExecutionContext.cs(构造函数节选)
    Activity = activity;                      // 正在执行的活动实例
    ActivityDescriptor = activityDescriptor;
    Status = ActivityStatus.Pending;
    ExpressionExecutionContext = new(...);    // 表达式求值上下文(变量读写从这里走)
    

    关键字段:

    • ParentActivityExecutionContext:结构上的父容器(比如 Sequence 包含 WriteHttpResponse
    • IsExecuting:执行中标志,意外宕机重启时依靠此标志恢复中断的活动
    • CallStackDepth:跨工作流调用栈深度
  • Bookmark:挂起点

    BookmarkModels/Bookmark.cs)代表工作流在某个活动处的挂起点,活动遇到需要等待外部信号(HTTP 回调、定时器等)时创建书签:

    // Models/Bookmark.cs
    public string Name { get; set; }        // 书签名称,用于匹配外部信号
    public string Hash { get; set; }        // 输入参数哈希,用于精确匹配
    public string ActivityId { get; set; }  // 关联的活动 ID
    public string? ActivityInstanceId { get; set; } // 关联的活动执行上下文 ID(string 类型)
    public bool AutoBurn { get; set; }      // 恢复后是否自动销毁
    public string? CallbackMethodName { get; set; } // 恢复时调用的回调方法名
    

三、执行管道:Elsa 的中间件管道架构

WorkflowRunner.RunAsync()
     ↓
IWorkflowExecutionPipeline.ExecuteAsync()   ← 工作流级管道
     ↓
DefaultActivitySchedulerMiddleware           ← 主循环中间件(调度器)
     ↓(对每个活动工作项)
IActivityInvoker.InvokeAsync()
     ↓
IActivityExecutionPipeline.ExecuteAsync()   ← 活动级管道
     ↓
DefaultActivityInvokerMiddleware             ← 活动执行中间件

两套管道都是标准的中间件链(类似 ASP.NET Core 的 RequestDelegate 链),每个中间件可以在调用 Next(context) 前后插入任意逻辑,方便横切关注点(日志、异常处理、通知发布等)的扩展。

四、执行入口:WorkflowRunner

WorkflowRunnerServices/WorkflowRunner.cs)是执行引擎的外部接口,提供多种重载的 RunAsync() 方法。核心路径有两条:

  • 全新启动Services/WorkflowRunner.cs 第69行):

    // Services/WorkflowRunner.cs 第69行
    public async Task<RunWorkflowResult> RunAsync(WorkflowGraph workflowGraph, RunWorkflowOptions? options = null, CancellationToken cancellationToken = default)
    {
        var instanceId = options?.WorkflowInstanceId ?? identityGenerator.GenerateId();
        var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(
            serviceProvider, workflowGraph, instanceId, correlationId, ...);
    
        // 把根活动加入调度队列
        workflowExecutionContext.ScheduleWorkflow(...);
    
        return await RunAsync(workflowExecutionContext);
    }
    
  • 从断点恢复Services/WorkflowRunner.cs 第114行):从 WorkflowState(持久化快照)还原出 WorkflowExecutionContext,然后根据触发条件决定调度什么:

    • BookmarkId:找到对应书签调度恢复
    • ActivityHandle:直接调度指定活动
    • 有已中断的活动(IsExecuting = true):重新调度它们(处理宕机恢复场景)
    • 什么都没有:重新 ScheduleWorkflow 从头开始
  • 两条路最终都汇入 RunAsync(WorkflowExecutionContext)(第206行),这是真正的执行入口:

    // Services/WorkflowRunner.cs 第206行
    public async Task<RunWorkflowResult> RunAsync(WorkflowExecutionContext workflowExecutionContext)
    {
        await notificationSender.SendAsync(new WorkflowExecuting(workflow, workflowExecutionContext), cancellationToken);
    
        if (workflowExecutionContext.SubStatus == WorkflowSubStatus.Pending)
        {
            workflowExecutionContext.TransitionTo(WorkflowSubStatus.Executing);
            await notificationSender.SendAsync(new WorkflowStarted(workflow, workflowExecutionContext), cancellationToken);
        }
    
        // 进入工作流执行管道(这里是中间件链的入口)
        await pipeline.ExecuteAsync(workflowExecutionContext);
    
        var workflowState = workflowStateExtractor.Extract(workflowExecutionContext);
    
        if (workflowState.Status == WorkflowStatus.Finished)
            await notificationSender.SendAsync(new WorkflowFinished(...), cancellationToken);
    
        await notificationSender.SendAsync(new WorkflowExecuted(...), cancellationToken);
        await commitStateHandler.CommitAsync(workflowExecutionContext, workflowState, cancellationToken);
        return new(workflowExecutionContext, workflowState, ...);
    } ```
    
    

五、主循环:DefaultActivitySchedulerMiddleware

  • WorkflowExecutionPipeline 的默认配置(Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs 第39行):

    // Pipelines/WorkflowExecution/WorkflowExecutionPipeline.cs 第39行
    private WorkflowMiddlewareDelegate CreateDefaultPipeline() => Setup(x => x
        .UseExceptionHandling()          // 全局异常捕获中间件
        .UseDefaultActivityScheduler()); // 活动调度主循环中间件
    
  • DefaultActivitySchedulerMiddlewareMiddleware/Workflows/DefaultActivitySchedulerMiddleware.cs)是执行引擎的心脏——调度循环

    // Middleware/Workflows/DefaultActivitySchedulerMiddleware.cs 第27行
    public override async ValueTask InvokeAsync(WorkflowExecutionContext context)
    {
        var scheduler = context.Scheduler;
    
        context.TransitionTo(WorkflowSubStatus.Executing);
        await ConditionallyCommitStateAsync(context, WorkflowLifetimeEvent.WorkflowExecuting);
    
        while (scheduler.HasAny)
        {
            if (context.CancellationToken.IsCancellationRequested)
                break;
    
            var currentWorkItem = scheduler.Take(); // 从队列取出一个工作项
            await ExecuteWorkItemAsync(context, currentWorkItem); // 执行它
        }
    
        await Next(context); // 调用管道中的下一个中间件
    
        if (context.Status == WorkflowStatus.Running)
            context.TransitionTo(context.AllActivitiesCompleted() ? WorkflowSubStatus.Finished : WorkflowSubStatus.Suspended);
    }
    

调度器是 QueueBasedActivitySchedulerServices/QueueBasedActivityScheduler.cs),底层是一个 Queue<ActivityWorkItem>,先进先出(FIFO)。
循环的退出条件:队列清空。如果队列清空时所有活动都已完成,状态变为 Finished;如果有活动挂起了(创建了书签但未完成),状态变为 Suspended——工作流"睡着了",等待下次唤醒。

六、单个活动的执行:从调度到完成

ActivityInvoker:把活动送入活动执行管道

每个工作项被取出后,由 ActivityInvoker.InvokeAsync()Services/ActivityInvoker.cs)处理:

// Services/ActivityInvoker.cs 第15行
public async Task<ActivityExecutionContext> InvokeAsync(WorkflowExecutionContext workflowExecutionContext, IActivity activity, ActivityInvocationOptions? options = null)
{
    // 创建或复用 ActivityExecutionContext
    activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContextAsync(activity, options);
    activityExecutionContext.Taint(); // 标记为"待执行"

    workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext);

    // 进入活动执行管道
    await InvokeAsync(activityExecutionContext);
    return activityExecutionContext;
}

public async Task InvokeAsync(ActivityExecutionContext activityExecutionContext)
{
    // 执行活动执行管道(IActivityExecutionPipeline)
    await pipeline.ExecuteAsync(activityExecutionContext);
}

活动执行管道:DefaultActivityInvokerMiddleware

活动执行管道默认注册了多个中间件(按顺序执行):

  • ExceptionHandlingMiddleware:捕获活动执行中的异常,支持 Incident 策略
  • ExecutionLogMiddleware:记录活动执行日志
  • NotificationPublishingMiddleware:发出 ActivityExecuting/ActivityExecuted 通知
  • LoggingMiddleware:结构化日志记录
  • DefaultActivityInvokerMiddleware:最核心的活动执行逻辑

DefaultActivityInvokerMiddlewareMiddleware/Activities/DefaultActivityInvokerMiddleware.cs)完成一个活动的完整执行流程:

// Middleware/Activities/DefaultActivityInvokerMiddleware.cs 第32行
public async ValueTask InvokeAsync(ActivityExecutionContext context)
{
    // 1. 求值输入属性(表达式求值)
    await EvaluateInputPropertiesAsync(context);

    // 2. 检查前置条件(CanExecuteAsync)
    if (!await context.Activity.CanExecuteAsync(context))
    {
        context.TransitionTo(ActivityStatus.Pending);
        return;
    }

    // 3. 设置 IsExecuting = true(防宕机恢复标志)
    using var executionState = context.EnterExecution();

    // 4. 可选:执行前提交状态快照(CommitStrategy 决定)
    if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuting))
        await context.WorkflowExecutionContext.CommitAsync();

    context.TransitionTo(ActivityStatus.Running);

    // 5. 执行活动(调用 activity.ExecuteAsync(context) 或执行委托)
    await ExecuteActivityAsync(context);

    // 6. 如果用书签恢复,AutoBurn 则销毁书签
    if (resumedBookmark is { AutoBurn: true })
        workflowExecutionContext.Bookmarks.Remove(resumedBookmark);

    context.IncrementExecutionCount();

    // 7. 调用管道中的下一个中间件
    await next(context);

    // 8. 如果活动完成,发出 ActivityCompleted 通知
    if (activityDidComplete)
        await mediator.SendAsync(new Notifications.ActivityCompleted(context), ...);

    // 9. 可选:执行后提交状态快照
    if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuted))
        await context.WorkflowExecutionContext.CommitAsync();
}

活动完成:CompleteActivityAsync

活动内部调用 context.CompleteActivityAsync() 表示自己执行完毕,核心逻辑如下:

// Contexts/ActivityExecutionContext.cs(CompleteActivityAsync 片段)
public async ValueTask CompleteActivityAsync(object? result = null)
{
    // 取消未完成的子活动
    foreach (var childContext in childContexts)
        await childContext.CancelActivityAsync();

    TransitionTo(ActivityStatus.Completed);
    AddExecutionLogEntry("Completed");

    // 发送 ActivityCompleted 信号(通知父容器活动完成)
    await this.SendSignalAsync(new ActivityCompleted(result));

    // 清除该活动创建的所有书签
    ClearBookmarks();
    WorkflowExecutionContext.Bookmarks.RemoveWhere(x => x.ActivityInstanceId == Id);
}

SendSignalAsync(new ActivityCompleted(result)) 向父容器发信号,父容器(如 SequenceFlowchart)收到信号后决定下一步调度哪个活动,由此形成了流程的"流转"。


七、挂起与恢复:Bookmark 机制

活动可以在执行中途创建书签,表示"我需要等外部信号"。典型场景:一个 HTTP Trigger 活动等待外部 HTTP 请求到来。

创建书签:活动在 ExecuteAsync 中调用 context.CreateBookmarkAsync(),书签被加入 WorkflowExecutionContext.Bookmarks。活动此时不调用 CompleteActivityAsync(),所以调度队列不会新增后续活动。循环体取完最后一个工作项、队列清空,循环退出,状态变为 Suspended

持久化WorkflowRunner 执行完成后调用 commitStateHandler.CommitAsync(),将状态(含书签)持久化到数据库。

恢复:外部信号到来时,运行时根据信号参数哈希匹配 Bookmark.Hash,找到对应的工作流实例,以 BookmarkId 为参数重新调用 WorkflowRunner.RunAsync(WorkflowGraph, WorkflowState, options)。恢复时 options.BookmarkId 非空:

// Services/WorkflowRunner.cs 第138行
var bookmark = workflowState.Bookmarks.FirstOrDefault(x => x.Id == bookmarkId);
if (bookmark != null)
    workflowExecutionContext.ScheduleBookmark(bookmark);

ScheduleBookmark 将对应的 ActivityExecutionContext 重新加入调度队列,书签携带的 CallbackMethodName 指定恢复时在活动类上调用的方法。执行流程从断点继续。

八、状态提取与还原:WorkflowStateExtractor

WorkflowStateExtractorServices/WorkflowStateExtractor.cs)负责内存对象与持久化状态之间的双向转换。

提取(Extract):把 WorkflowExecutionContext 序列化为可持久化的 WorkflowState。提取的内容包括:实例 ID、状态标志、书签列表、所有活动执行上下文快照、完成回调、调度队列中待处理的工作项等。

还原:在 WorkflowExecutionContext.CreateAsync() 时,如果传入了 WorkflowState,会调用内部的 Apply 方法把所有字段反向写回 WorkflowExecutionContext,重建所有活动执行上下文、书签、调度队列等,让工作流能从上次中断的地方继续。

九、变量管理:VariablePersistenceManager

VariablePersistenceManagerServices/VariablePersistenceManager.cs)管理变量在内存寄存器与外部存储之间的读写。

变量通过 StorageDriver 存储,不同变量可以配置不同的存储驱动(内存存储、数据库存储等)。加载时(LoadVariablesAsync):

// Services/VariablePersistenceManager.cs
foreach (var context in contexts)
{
    foreach (var variable in GetLocalVariables(context))
    {
        context.ExpressionExecutionContext.Memory.Declare(variable); // 在内存寄存器中声明变量槽位
        var driver = storageDriverManager.Get(metadata.StorageDriverType!);
        var value = await driver.ReadAsync(id, storageDriverContext); // 从存储驱动读取值
        variable.Set(register, value); // 写入内存寄存器
    }
}

活动通过 context.ExpressionExecutionContext.Memory 读写变量,VariablePersistenceManager 在活动执行前加载、活动完成后清理(DeleteVariablesAsync)。

十、Commit 数据落盘策略:

一次工作流执行可能包含几十个活动,不可能每步都写数据库,提交时机由 Commit 策略控制。

每个工作流和每个活动都可以配置 CommitStrategyName,指向一个 IWorkflowCommitStrategyIActivityCommitStrategy 实现。DefaultActivityInvokerMiddleware 在两个时机询问策略:

// Middleware/Activities/DefaultActivityInvokerMiddleware.cs 第61行
if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuting))
    await context.WorkflowExecutionContext.CommitAsync(); // 执行前提交

if (ShouldCommit(context, ActivityLifetimeEvent.ActivityExecuted))
    await context.WorkflowExecutionContext.CommitAsync(); // 执行后提交

策略返回三种值:Commit(立即提交)、Skip(跳过)、Default(回退到工作流级策略判断)。这套机制让开发者可以精确控制持久化粒度。

十一、整体执行流程总结

WorkflowRunner.RunAsync(WorkflowGraph)
  → WorkflowExecutionContext.CreateAsync()        // 创建执行上下文
  → ScheduleWorkflow()                            // 把根活动加入调度队列
  → RunAsync(WorkflowExecutionContext)
      → notificationSender.SendAsync(WorkflowExecuting)
      → notificationSender.SendAsync(WorkflowStarted)(首次执行)
      → IWorkflowExecutionPipeline.ExecuteAsync() // 进入工作流中间件管道
          ↓
      ExceptionHandlingMiddleware → Next()
          ↓
      DefaultActivitySchedulerMiddleware
          while (scheduler.HasAny)
              Take() → ActivityWorkItem
              ActivityInvoker.InvokeAsync()
                  → CreateActivityExecutionContextAsync()
                  → IActivityExecutionPipeline.ExecuteAsync()  // 进入活动中间件管道
                      ExceptionHandlingMiddleware → Next()
                      ExecutionLogMiddleware → Next()
                      NotificationPublishingMiddleware → Next()
                      LoggingMiddleware → Next()
                      DefaultActivityInvokerMiddleware
                          → EvaluateInputPropertiesAsync()    // 表达式求值
                          → CanExecuteAsync()                 // 前置条件检查
                          → EnterExecution()                  // IsExecuting = true
                          → CommitAsync()(如有策略)
                          → TransitionTo(Running)
                          → ExecuteActivityAsync()            // activity.ExecuteAsync(context)
                              ├─ 普通活动: CompleteActivityAsync()
                              │    → SendSignal(ActivityCompleted) → 父容器调度后续
                              └─ 等待活动: CreateBookmark()
                                    → 不完成 → 队列无新增
                          → AutoBurn bookmark(如适用)
                          → ActivityCompleted 通知
                          → CommitAsync()(如有策略)
          ↓ 队列清空
      状态 = AllCompleted ? Finished : Suspended
      ↓
  WorkflowStateExtractor.Extract()               // 提取状态快照
  → notificationSender.SendAsync(WorkflowFinished)(如已完成)
  → notificationSender.SendAsync(WorkflowExecuted)
  → CommitStateHandler.CommitAsync()             // 持久化最终状态

下一篇:Elsa.Workflows.Runtime — 触发器与书签队列详解

posted @ 2026-04-12 14:37  叨奈特挖井人  阅读(63)  评论(0)    收藏  举报