.NET CoreWF:如何跨线程在执行中途暂停WorkflowApplication?
基于CoreWF的工作流中途暂停解决方案
针对你的问题,核心难点在于无空闲点的同步工作流无法直接响应外部线程的暂停请求,以及内存字典无法应对应用重启的场景,以下是具体的实现方案:
一、跨线程暂停的可行性说明
直接在外部线程调用WorkflowApplication.Unload()无法让正在同步执行的工作流立即暂停——因为此时工作流引擎正处于同步代码执行阶段,没有进入空闲状态,无法处理外部命令。必须在工作流的执行步骤中插入主动检查逻辑,让工作流能在执行过程中响应暂停请求。
二、具体实现方案
1. 用持久化存储替代内存字典
放弃内存ConcurrentDictionary,改用数据库存储工作流实例的暂停请求(比如新增一张WorkflowPauseRequests表,字段包含InstanceId(Guid)、IsPauseRequested(bool)),彻底解决应用重启后丢失引用的问题。
2. 在自定义NativeActivity中加入暂停检查点
在每个步骤的执行逻辑中,插入暂停请求检查,一旦检测到暂停标记,主动触发工作流持久化并卸载:
protected override void Execute(NativeActivityContext context) { // 执行当前步骤的核心逻辑 RunSql("SELECT * FROM Table1"); // 第一次检查暂停请求 var instanceId = context.WorkflowInstanceId; if (IsPauseRequested(instanceId)) { context.Unload(); // 持久化并卸载工作流 return; // 终止当前步骤,不调度下一个步骤 } WaitForOneMinute(); // 第二次检查暂停请求(关键步骤后建议都加检查) if (IsPauseRequested(instanceId)) { context.Unload(); return; } // 无暂停请求,继续执行下一个步骤 context.ScheduleActivity(this.NextStep); } // 从数据库查询指定实例的暂停请求状态 private bool IsPauseRequested(Guid instanceId) { using var conn = new SqlConnection("你的数据库连接字符串"); conn.Open(); var cmd = new SqlCommand("SELECT IsPauseRequested FROM WorkflowPauseRequests WHERE InstanceId = @instanceId", conn); cmd.Parameters.AddWithValue("@instanceId", instanceId); var result = cmd.ExecuteScalar(); return result != null && (bool)result; }
3. UI触发暂停的逻辑
用户点击“暂停”按钮时,只需向数据库写入对应工作流实例的暂停请求,不需要持有WorkflowApplication的内存引用:
// UI按钮点击事件处理 public void OnPauseButtonClick(Guid workflowInstanceId) { using var conn = new SqlConnection("你的数据库连接字符串"); conn.Open(); // 插入或更新暂停请求 var cmd = new SqlCommand(@" MERGE INTO WorkflowPauseRequests t USING (SELECT @instanceId AS InstanceId, 1 AS IsPauseRequested) s ON t.InstanceId = s.InstanceId WHEN MATCHED THEN UPDATE SET t.IsPauseRequested = 1 WHEN NOT MATCHED THEN INSERT (InstanceId, IsPauseRequested) VALUES (@instanceId, 1); ", conn); cmd.Parameters.AddWithValue("@instanceId", workflowInstanceId); cmd.ExecuteNonQuery(); }
4. 配置CoreWF持久化与重启恢复
使用CoreWF的SqlInstanceStore实现工作流状态的持久化,确保应用重启后能恢复未完成的工作流:
// 初始化实例存储 var instanceStore = new SqlInstanceStore("你的数据库连接字符串"); // 启动工作流时配置持久化 var wfApp = new WorkflowApplication(yourWorkflowDefinition); wfApp.InstanceStore = instanceStore; // 设置工作流空闲时自动卸载并持久化 wfApp.PersistableIdle = (e) => PersistableIdleAction.Unload; // 工作流卸载后清理暂停请求标记 wfApp.Unloaded = (e) => { using var conn = new SqlConnection("你的数据库连接字符串"); conn.Open(); var cmd = new SqlCommand("DELETE FROM WorkflowPauseRequests WHERE InstanceId = @instanceId", conn); cmd.Parameters.AddWithValue("@instanceId", e.InstanceId); cmd.ExecuteNonQuery(); }; // 启动工作流并记录实例ID到业务表(方便UI关联) var instanceId = wfApp.Id; SaveWorkflowInstanceToDb(instanceId, "Running"); wfApp.Run();
应用重启后,从数据库加载所有处于运行状态的工作流实例并恢复执行:
// 从业务表获取所有未完成的工作流实例ID var runningInstanceIds = GetRunningWorkflowInstanceIdsFromDb(); foreach (var instanceId in runningInstanceIds) { var wfApp = new WorkflowApplication(yourWorkflowDefinition); wfApp.InstanceStore = instanceStore; wfApp.PersistableIdle = (e) => PersistableIdleAction.Unload; wfApp.Unloaded = (e) => ClearPauseRequest(e.InstanceId); wfApp.Load(instanceId); wfApp.Run(); }
三、补充说明
- 如果不需要保留工作流状态,仅需强制终止,可以调用
WorkflowApplication.Abort(),但此操作不可逆,适合不需要恢复的场景。 - 检查点的插入位置可以根据步骤复杂度调整,比如在耗时操作前后、循环内部等关键节点加入,平衡暂停响应及时性和性能开销。
内容的提问来源于stack exchange,提问作者Jerryalafoly
相关产品推荐
相关产品推荐

