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:根节点(通常是Sequence或Flowchart)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:工作流上下文
WorkflowExecutionContext(Contexts/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:节点上下文
ActivityExecutionContext(Contexts/ActivityExecutionContext.cs)是工作流执行上下文的"子集",代表某次具体的活动执行:// Contexts/ActivityExecutionContext.cs(构造函数节选) Activity = activity; // 正在执行的活动实例 ActivityDescriptor = activityDescriptor; Status = ActivityStatus.Pending; ExpressionExecutionContext = new(...); // 表达式求值上下文(变量读写从这里走)关键字段:
ParentActivityExecutionContext:结构上的父容器(比如Sequence包含WriteHttpResponse)IsExecuting:执行中标志,意外宕机重启时依靠此标志恢复中断的活动CallStackDepth:跨工作流调用栈深度
-
Bookmark:挂起点
Bookmark(Models/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
WorkflowRunner(Services/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()); // 活动调度主循环中间件 -
DefaultActivitySchedulerMiddleware(Middleware/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); }
调度器是
QueueBasedActivityScheduler(Services/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:最核心的活动执行逻辑
DefaultActivityInvokerMiddleware(Middleware/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)) 向父容器发信号,父容器(如 Sequence、Flowchart)收到信号后决定下一步调度哪个活动,由此形成了流程的"流转"。
七、挂起与恢复: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
WorkflowStateExtractor(Services/WorkflowStateExtractor.cs)负责内存对象与持久化状态之间的双向转换。
提取(Extract):把 WorkflowExecutionContext 序列化为可持久化的 WorkflowState。提取的内容包括:实例 ID、状态标志、书签列表、所有活动执行上下文快照、完成回调、调度队列中待处理的工作项等。
还原:在 WorkflowExecutionContext.CreateAsync() 时,如果传入了 WorkflowState,会调用内部的 Apply 方法把所有字段反向写回 WorkflowExecutionContext,重建所有活动执行上下文、书签、调度队列等,让工作流能从上次中断的地方继续。
九、变量管理:VariablePersistenceManager
VariablePersistenceManager(Services/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,指向一个 IWorkflowCommitStrategy 或 IActivityCommitStrategy 实现。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 — 触发器与书签队列详解

浙公网安备 33010602011771号