文章同步在公众号与知乎更新,账号全部同名。搜索“小黄花呀小黄花”即可找到我。
前言
上一篇文章中,我们介绍了流程引擎与上下文模块的实现,奠定了流程执行的基础。
本文将进一步深入,重点介绍流程执行的核心单元 —— 单步流程步骤(FlowStep)的接口设计、基类封装及典型实现方式(如延时、跳转、循环、并行等)。
本文还将说明如何在步骤中灵活设置下一步逻辑,实现高度可配置的流程控制。
类图
代码实现
单步流程接口
接口中包含一个名称,和一个可等待的方法。
public interface IFlowStep
{
/// <summary>
/// 步骤名称
/// </summary>
string StepName { get; }
/// <summary>
/// 执行步骤可等待方法
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
Task<StepResult> AsyncExecuteStep(IFlowContext context, CancellationToken token = default);
}
单步流程基类实现
接口完成后,实现了一个基类,后续所有的步骤实现都继承整个类。
AsyncExecuteStep方法为步骤的核心方法,流程调用时就执行该方法,该方法返回一个结果,流程也会更具该结果进行判断和执行。AsyncExecuteStep内部会调用AsyncExecuteCore方法,子类后续只要实现两个抽象方法AsyncExecuteCore和SetNextStepIndex即可。
同时基类中实现了AsyncExecuteBranch方法,这个方法主要是用于循环和并行时使用,可以直接执行一段步骤。这个方法中还需要将上下文(流程引擎实现)分开操作,data可以公用,但是下一步这些的参数就是各自分支单独的。
public abstract class FlowStepBase : IFlowStep
{
#region 构造函数
protected FlowStepBase(string stepName)
{
StepName = stepName;
}
#endregion
#region 属性
public string StepName { get; }
#endregion
#region 方法
/// <summary>
/// 执行该步骤的可等待方法,此方法可重写
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
public virtual async Task<StepResult> AsyncExecuteStep(IFlowContext context, CancellationToken token = default)
{
try
{
await AsyncExecuteCore(context, token);
return StepResult.Success();
}
catch (OperationCanceledException)
{
return StepResult.Cancelled($"步骤 {StepName} 被取消");
}
catch (Exception e)
{
return StepResult.Failure($"步骤 {StepName} 执行失败: {e.Message}");
}
}
/// <summary>
/// 步骤的主要执行内容
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
protected abstract Task AsyncExecuteCore(IFlowContext context, CancellationToken token);
/// <summary>
/// 设置下一步索引,用在ExecuteCoreAsync中
/// </summary>
/// <param name="context"></param>
protected abstract void SetNextStepIndex(IFlowContext context);
/// <summary>
/// 当需要在步骤中执行一段流程时使用
/// 如:循环/并行
/// </summary>
/// <param name="context"></param>
/// <param name="token"></param>
/// <param name="branch">流程</param>
/// <param name="branchIndex">流程分支号</param>
/// <returns></returns>
protected async Task<StepResult> AsyncExecuteBranch(IFlowContext context, CancellationToken token, List<IFlowStep> branch, int branchIndex)
{
var branchContext = context.Clone();
branchContext.NextStepIndex = 0;
branchContext.TotalSteps = branch.Count;
int index = 0;
while (!token.IsCancellationRequested && index < branch.Count)
{
var _currentStep = branch[index];
if (_currentStep != null)
{
context.Logger.Info($"步骤{StepName}分支{branchIndex}:开始执行步骤【{_currentStep.StepName}】...");
var result = await _currentStep.AsyncExecuteStep(branchContext, token);
if (result.Status != StepStatus.Success)
{
context.Logger.Error($"步骤{StepName}分支{branchIndex}:步骤【{_currentStep.StepName}】执行失败:{result.Message}");
return result;
}
}
index = branchContext.NextStepIndex;
}
context.Logger.Info($"步骤{StepName}分支{branchIndex}执行完成");
return StepResult.Success();
}
#endregion
}
单步流程实际步骤实现
所有的实际步骤都继承自FlowStepBase。这些实现正常应该放在Application中,但是为了更好的编写基类,所以优先实现了几个特殊的步骤.
而他们的构造方法中的参数,当前先用构造方法注入,后续计划改成属性注入。
延时
public class FlowStep_Delay : FlowStepBase
{
#region 构造函数
/// <summary>
/// 延时步骤构造函数
/// </summary>
/// <param name="stepName">步骤名称</param>
/// <param name="delayTime_ms">延时时间ms</param>
public FlowStep_Delay(string stepName, int delayTime_ms) : base(stepName)
{
_delayTime_ms = delayTime_ms;
}
#endregion
#region 字段
private readonly int _delayTime_ms;
#endregion
#region 方法
protected override async Task AsyncExecuteCore(IFlowContext context, CancellationToken token)
{
await Task.Delay(_delayTime_ms, token);
SetNextStepIndex(context);
}
protected override void SetNextStepIndex(IFlowContext context)
{
context.NextStepIndex++;
}
#endregion
}
跳转
public class FlowStep_JumpTo : FlowStepBase
{
#region 构造函数
public FlowStep_JumpTo(string stepName, int jumpToStepIndex) : base(stepName)
{
_jumpToStepIndex = jumpToStepIndex;
}
#endregion
#region 字段
private readonly int _jumpToStepIndex;
#endregion
#region 方法
protected override async Task AsyncExecuteCore(IFlowContext context, CancellationToken token)
{
if (_jumpToStepIndex >= 0 && _jumpToStepIndex < context.TotalSteps)
{
SetNextStepIndex(context);
}
else
{
string msg = $"步骤{StepName}设置步数{_jumpToStepIndex}错误,超出总步数{context.TotalSteps}";
context.Logger.Error(msg);
throw new StepExecuteException(msg);
}
await Task.CompletedTask;
}
protected override void SetNextStepIndex(IFlowContext context)
{
context.NextStepIndex = _jumpToStepIndex;
}
#endregion
}
循环
public class FlowStep_Loop : FlowStepBase
{
#region 构造函数
public FlowStep_Loop(string stepName, List<IFlowStep> loopBranch, int totalLoops) : base(stepName)
{
_loopBranch = loopBranch ?? throw new ArgumentNullException(nameof(loopBranch));
_totalLoops = totalLoops;
}
#endregion
#region 属性
#endregion
#region 字段
private readonly List<IFlowStep> _loopBranch;
private readonly int _totalLoops;
#endregion
#region 方法
protected override async Task AsyncExecuteCore(IFlowContext context, CancellationToken token)
{
for (int i = 0; i < _totalLoops; i++)
{
context.Logger.Info($"步骤{StepName}第{i}/{_totalLoops}次循环开始");
await AsyncExecuteBranch(context, token, _loopBranch, i);
context.Logger.Info($"步骤{StepName}第{i}/{_totalLoops}次循环完成始");
}
SetNextStepIndex(context);
}
protected override void SetNextStepIndex(IFlowContext context)
{
context.NextStepIndex++;
}
#endregion
}
并行
public class FlowStep_Parallel : FlowStepBase
{
#region 构造函数
public FlowStep_Parallel(string stepName, List<List<IFlowStep>> branches, bool waitAll = true)
: base(stepName)
{
_parallelBranches = branches ?? throw new ArgumentNullException(nameof(branches));
_waitAll = waitAll;
}
#endregion
#region 字段
private readonly List<List<IFlowStep>> _parallelBranches;
private readonly bool _waitAll;
#endregion
#region 方法
protected override async Task AsyncExecuteCore(IFlowContext context, CancellationToken token)
{
StepResult stepResult = null;
var branchTasks = new List<Task<StepResult>>();
for (int i = 0; i < _parallelBranches.Count; i++)
{
branchTasks.Add(AsyncExecuteBranch(context, token, _parallelBranches[i], i));
}
if (_waitAll)
{
var branchResults = await Task.WhenAll(branchTasks);
stepResult = branchResults.All(x => x.Status == StepStatus.Success)
? StepResult.Success() : StepResult.Failure("部分分支执行失败");
}
else
{
var completed = await Task.WhenAny(branchTasks);
stepResult = await completed;
}
if (stepResult.Status != StepStatus.Success)
{
throw new StepExecuteException($"并行步骤 {StepName} 失败");
}
SetNextStepIndex(context);
}
protected override void SetNextStepIndex(IFlowContext context)
{
context.NextStepIndex++;
}
#endregion
}
运行结果
创建了一个结果类,里添加静态方法,用于步骤结果的返回,可以同时返回结果与信息。
public class StepResult
{
public StepStatus Status { get; set; }
public string Message { get; set; }
public static StepResult Success() =>
new StepResult() { Status = StepStatus.Success };
public static StepResult Failure(string msg) =>
new StepResult() { Status = StepStatus.Failure, Message = msg };
public static StepResult Timeout(string msg) =>
new StepResult() { Status = StepStatus.Timeout, Message = msg };
public static StepResult Cancelled(string msg) =>
new StepResult() { Status = StepStatus.Cancelled, Message = msg };
public static StepResult Skipped(string msg) =>
new StepResult() { Status = StepStatus.Skipped, Message = msg };
}
public enum StepStatus
{
Success,
Failure,
Timeout,
Cancelled,
Skipped
}
后记
本文完成了流程步骤模块的接口、基类与典型实现,构建了一个模块化的流程步骤系统,为后续支持各类工业动作、逻辑分支、数据处理提供了基础。
后续我将继续实现流程引擎中的状态机管理、事件系统等核心功能,并不断扩展实际流程步骤,逐步完善整个工业自动化软件框架。
欢迎关注专栏查看以往内容以及后续更新。构建工业自动化软件框架
代码放在了Github上,欢迎围观,欢迎 Star/Fork/ 提Issue。 ·
知乎公众号同步更新,欢迎关注公众号。