mirror of
https://gitee.com/langsisi_admin/serein-flow
synced 2026-04-04 23:36:35 +08:00
增加流程运行特性:支持异步节点
This commit is contained in:
@@ -9,15 +9,15 @@ namespace Serein.Library.Enums
|
||||
public enum NodeType
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化(事件,不生成节点)
|
||||
/// 初始化,流程启动时执行(不生成节点)
|
||||
/// </summary>
|
||||
Init,
|
||||
/// <summary>
|
||||
/// 开始载入(事件,不生成节点)
|
||||
/// 开始载入,流程启动时执行(不生成节点)
|
||||
/// </summary>
|
||||
Loading,
|
||||
/// <summary>
|
||||
/// 结束(事件,不生成节点)
|
||||
/// 结束,流程结束时执行(不生成节点)
|
||||
/// </summary>
|
||||
Exit,
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
using Net462DllTest.Signal;
|
||||
using Net462DllTest.Trigger;
|
||||
using Net462DllTest.ViewModel;
|
||||
using Net462DllTest.Trigger;
|
||||
using Serein.Library.Api;
|
||||
using Serein.Library.Attributes;
|
||||
using Serein.Library.Enums;
|
||||
@@ -9,9 +6,6 @@ using Serein.Library.Ex;
|
||||
using Serein.Library.Framework.NodeFlow;
|
||||
using Serein.Library.NodeFlow.Tool;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.LogicControl
|
||||
@@ -79,5 +73,7 @@ namespace Net462DllTest.LogicControl
|
||||
Console.WriteLine("发送命令失败:调取车位" + spaceNum);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace Net462DllTest.LogicControl
|
||||
}
|
||||
|
||||
#region 初始化、初始化完成以及退出的事件
|
||||
[NodeAction(NodeType.Init)] // Init : 初始化事件,流程启动时执行
|
||||
[NodeAction(NodeType.Init)]
|
||||
public void Init(IDynamicContext context)
|
||||
{
|
||||
context.Env.IOC.Register<IRouter, Router>();
|
||||
@@ -108,6 +108,15 @@ namespace Net462DllTest.LogicControl
|
||||
|
||||
#region 动作节点
|
||||
|
||||
[NodeAction(NodeType.Action, "等待")]
|
||||
public async Task Delay(int ms = 5000)
|
||||
{
|
||||
await Console.Out.WriteLineAsync("开始等待");
|
||||
await Task.Delay(ms);
|
||||
await Console.Out.WriteLineAsync("不再等待");
|
||||
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "PLC初始化")]
|
||||
public SiemensPlcDevice PlcInit(SiemensVersion version = SiemensVersion.None,
|
||||
string ip = "192.168.10.100",
|
||||
@@ -153,7 +162,7 @@ namespace Net462DllTest.LogicControl
|
||||
}
|
||||
|
||||
[NodeAction(NodeType.Action, "PLC写入变量")]
|
||||
public SiemensPlcDevice WriteVar2(object value, PlcVarName varName)
|
||||
public SiemensPlcDevice WriteVar(object value, PlcVarName varName)
|
||||
{
|
||||
var varInfo = varName.ToVarInfo();
|
||||
if (MyPlc.State == PlcState.Runing)
|
||||
@@ -176,7 +185,6 @@ namespace Net462DllTest.LogicControl
|
||||
return MyPlc;
|
||||
}
|
||||
|
||||
|
||||
[NodeAction(NodeType.Action, "批量读取")]
|
||||
public void BatchReadVar()
|
||||
{
|
||||
|
||||
33
Net462DllTest/Main.cs
Normal file
33
Net462DllTest/Main.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.Properties
|
||||
{
|
||||
/*
|
||||
理想的项目架构:
|
||||
FlowEnv - LoginControl:
|
||||
|
||||
|
||||
LoginControl
|
||||
↙ ↘
|
||||
(View-Interaction) (Node-Interaction)
|
||||
↓ ↕
|
||||
View ←→ ViewModel ←→ Trigger ← SingleEnum
|
||||
↓
|
||||
Model
|
||||
· DataChanged → Trigger
|
||||
|
||||
|
||||
视图驱动触发器,触发器驱动数据。
|
||||
数据驱动触发器,触发器驱动视图。
|
||||
|
||||
所以,这个结构≈事件驱动。
|
||||
|
||||
|
||||
动态的配置事件触发的原因、过程与结果。
|
||||
|
||||
*/
|
||||
}
|
||||
@@ -9,21 +9,6 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Net462DllTest.Model
|
||||
{
|
||||
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class PlcValueAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 变量类型
|
||||
/// </summary>
|
||||
public PlcVarName PlcVarEnum { get; }
|
||||
public PlcValueAttribute(PlcVarName plcVarEnum)
|
||||
{
|
||||
this.PlcVarEnum = plcVarEnum;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// PLC变量
|
||||
/// </summary>
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Model\PlcVarModel.cs" />
|
||||
<Compile Include="Main.cs" />
|
||||
<Compile Include="Trigger\SiemensPlcDevice.cs" />
|
||||
<Compile Include="Trigger\PrakingDevice.cs" />
|
||||
<Compile Include="Enums\PlcState.cs" />
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Net462DllTest.Trigger
|
||||
[AutoRegister]
|
||||
public class PrakingDevice : ChannelFlowTrigger<ParkingCommand>
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace Net462DllTest.Utils
|
||||
il.Emit(OpCodes.Callvirt, property.GetSetMethod()); // 调用属性的Setter方法
|
||||
il.Emit(OpCodes.Ret); // 返回
|
||||
|
||||
|
||||
|
||||
return (Action<TModel, object>)method.CreateDelegate(typeof(Action<TModel, object>));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@ using System.Windows.Input;
|
||||
|
||||
namespace Net462DllTest.Utils
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于窗体View与ViewModel进行交互
|
||||
/// </summary>
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action<object> _execute;
|
||||
|
||||
@@ -6,8 +6,10 @@ using Serein.Library.Entity;
|
||||
using Serein.Library.Enums;
|
||||
using Serein.Library.Ex;
|
||||
using Serein.Library.Utils;
|
||||
using Serein.NodeFlow.Tool;
|
||||
using Serein.NodeFlow.Tool.SereinExpression;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
@@ -101,48 +103,48 @@ namespace Serein.NodeFlow.Base
|
||||
{
|
||||
Stack<NodeModelBase> stack = new Stack<NodeModelBase>();
|
||||
stack.Push(this);
|
||||
var cts = context.Env.IOC.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
|
||||
var flowCts = context.Env.IOC.Get<CancellationTokenSource>(FlowStarter.FlipFlopCtsName);
|
||||
while (stack.Count > 0 ) // 循环中直到栈为空才会退出循环
|
||||
{
|
||||
if(cts is not null)
|
||||
if(flowCts is not null)
|
||||
{
|
||||
if (cts.IsCancellationRequested)
|
||||
if (flowCts.IsCancellationRequested)
|
||||
break;
|
||||
}
|
||||
// 节点执行异常时跳过执行
|
||||
|
||||
// 从栈中弹出一个节点作为当前节点进行处理
|
||||
var currentNode = stack.Pop();
|
||||
|
||||
//// 设置方法执行的对象
|
||||
//if (currentNode.MethodDetails?.ActingInstance is not null && currentNode.MethodDetails?.ActingInstanceType is not null)
|
||||
//{
|
||||
// currentNode.MethodDetails.ActingInstance = context.Env.IOC.GetOrRegisterInstantiate(currentNode.MethodDetails.ActingInstanceType);
|
||||
//}
|
||||
|
||||
#region 执行相关
|
||||
|
||||
// 首先执行上游分支
|
||||
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream];
|
||||
for (int i = upstreamNodes.Count - 1; i >= 0; i--)
|
||||
// 筛选出上游分支
|
||||
var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream].Where(
|
||||
node => node.DebugSetting.IsEnable
|
||||
).ToArray();
|
||||
// 执行上游分支
|
||||
foreach (var upstreamNode in upstreamNodes)
|
||||
{
|
||||
// 筛选出启用的节点
|
||||
if (upstreamNodes[i].DebugSetting.IsEnable)
|
||||
if (upstreamNode.DebugSetting.IsEnable)
|
||||
{
|
||||
if (upstreamNodes[i].DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前
|
||||
if (upstreamNode.DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前
|
||||
{
|
||||
var cancelType = await upstreamNodes[i].DebugSetting.GetInterruptTask();
|
||||
await Console.Out.WriteLineAsync($"[{upstreamNodes[i]?.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支");
|
||||
var cancelType = await upstreamNode.DebugSetting.GetInterruptTask();
|
||||
await Console.Out.WriteLineAsync($"[{upstreamNode.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支");
|
||||
}
|
||||
upstreamNode.PreviousNode = currentNode;
|
||||
await upstreamNode.StartExecute(context); // 执行流程节点的上游分支
|
||||
if (upstreamNode.NextOrientation == ConnectionType.IsError)
|
||||
{
|
||||
// 如果上游分支执行失败,不再继续执行
|
||||
// 使上游节点(仅上游节点本身,不包含上游节点的后继节点)
|
||||
// 具备通过抛出异常中断流程的能力
|
||||
break;
|
||||
}
|
||||
upstreamNodes[i].PreviousNode = currentNode;
|
||||
await upstreamNodes[i].StartExecute(context); // 执行流程节点的上游分支
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 执行当前节点
|
||||
|
||||
// 上游分支执行完成,才执行当前节点
|
||||
object? newFlowData = await currentNode.ExecutingAsync(context);
|
||||
if (cts is null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None)
|
||||
if (flowCts is null || flowCts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None)
|
||||
{
|
||||
// 不再执行
|
||||
break;
|
||||
@@ -159,14 +161,9 @@ namespace Serein.NodeFlow.Base
|
||||
// 将下一个节点集合中的所有节点逆序推入栈中
|
||||
for (int i = nextNodes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// 筛选出启用的节点、未被中断的节点
|
||||
if (nextNodes[i].DebugSetting.IsEnable /*&& nextNodes[i].DebugSetting.InterruptClass == InterruptClass.None*/)
|
||||
// 筛选出启用的节点的节点
|
||||
if (nextNodes[i].DebugSetting.IsEnable)
|
||||
{
|
||||
if (nextNodes[i].DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前
|
||||
{
|
||||
var cancelType = await nextNodes[i].DebugSetting.GetInterruptTask();
|
||||
await Console.Out.WriteLineAsync($"[{nextNodes[i]?.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支");
|
||||
}
|
||||
nextNodes[i].PreviousNode = currentNode;
|
||||
stack.Push(nextNodes[i]);
|
||||
}
|
||||
@@ -176,7 +173,7 @@ namespace Serein.NodeFlow.Base
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 执行节点对应的方法
|
||||
/// </summary>
|
||||
@@ -186,14 +183,9 @@ namespace Serein.NodeFlow.Base
|
||||
{
|
||||
#region 调试中断
|
||||
|
||||
if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前
|
||||
if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发检查是否需要中断
|
||||
{
|
||||
var cancelType = await this.DebugSetting.GetInterruptTask();
|
||||
//if(cancelType == CancelType.Discard)
|
||||
//{
|
||||
// this.NextOrientation = ConnectionType.None;
|
||||
// return null;
|
||||
//}
|
||||
var cancelType = await this.DebugSetting.GetInterruptTask(); // 等待中断结束
|
||||
await Console.Out.WriteLineAsync($"[{this.MethodDetails?.MethodName}]中断已{cancelType},开始执行后继分支");
|
||||
}
|
||||
|
||||
@@ -212,19 +204,41 @@ namespace Serein.NodeFlow.Base
|
||||
md.ActingInstance ??= context.Env.IOC.Get(md.ActingInstanceType);
|
||||
object instance = md.ActingInstance;
|
||||
|
||||
var haveParameter = md.ExplicitDatas.Length > 0;
|
||||
var haveResult = md.ReturnType != typeof(void);
|
||||
bool haveParameter = md.ExplicitDatas.Length > 0;
|
||||
bool haveResult = md.ReturnType != typeof(void);
|
||||
Type? taskResult = null;
|
||||
bool isTask = md.ReturnType is not null && MethodDetailsHelperTmp.IsGenericTask(md.ReturnType, out taskResult);
|
||||
bool isTaskHaveResult = taskResult is not null;
|
||||
object? result;
|
||||
|
||||
Console.WriteLine($"(isTask, isTaskHaveResult):{(isTask, isTaskHaveResult)}");
|
||||
try
|
||||
{
|
||||
// Action/Func([方法作用的实例],[可能的参数值],[可能的返回值])
|
||||
|
||||
object?[]? parameters = GetParameters(context, this, md);
|
||||
object? result = (haveParameter, haveResult) switch
|
||||
if (isTask)
|
||||
{
|
||||
(false, false) => Execution((Action<object>)del, instance), // 调用节点方法,返回null
|
||||
(true, false) => Execution((Action<object, object?[]?>)del, instance, parameters), // 调用节点方法,返回null
|
||||
(false, true) => Execution((Func<object, object?>)del, instance), // 调用节点方法,返回方法传回类型
|
||||
(true, true) => Execution((Func<object, object?[]?, object?>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法忏悔类型
|
||||
};
|
||||
// 异步方法(因为返回了Task,所以排除Action<>委托的可能)
|
||||
result = (haveParameter, isTaskHaveResult) switch
|
||||
{
|
||||
(false, false) => await ExecutionAsync((Func<object, Task>)del, instance), // 调用节点方法,返回方法传回类型
|
||||
(true, false) => await ExecutionAsync((Func<object, object?[]?, Task>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法返回类型
|
||||
(false, true) => await ExecutionAsync((Func<object, Task<object?>>)del, instance), // 调用节点方法,返回方法传回类型
|
||||
(true, true) => await ExecutionAsync((Func<object, object?[]?, Task<object?>>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法返回类型
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非异步方法
|
||||
result = (haveParameter, haveResult) switch
|
||||
{
|
||||
(false, false) => Execution((Action<object>)del, instance), // 调用节点方法,返回null
|
||||
(true, false) => Execution((Action<object, object?[]?>)del, instance, parameters), // 调用节点方法,返回null
|
||||
(false, true) => Execution((Func<object, object?>)del, instance), // 调用节点方法,返回方法传回类型
|
||||
(true, true) => Execution((Func<object, object?[]?, object?>)del, instance, parameters), // 调用节点方法,获取入参参数,返回方法返回类型
|
||||
};
|
||||
}
|
||||
|
||||
NextOrientation = ConnectionType.IsSucceed;
|
||||
return result;
|
||||
@@ -242,21 +256,41 @@ namespace Serein.NodeFlow.Base
|
||||
#region 节点转换的委托类型
|
||||
public static object? Execution(Action<object> del, object instance)
|
||||
{
|
||||
del?.Invoke(instance);
|
||||
del.Invoke(instance);
|
||||
return null;
|
||||
}
|
||||
public static object? Execution(Action<object, object?[]?> del, object instance, object?[]? parameters)
|
||||
{
|
||||
del?.Invoke(instance, parameters);
|
||||
del.Invoke(instance, parameters);
|
||||
return null;
|
||||
}
|
||||
public static object? Execution(Func<object, object?> del, object instance)
|
||||
{
|
||||
return del?.Invoke(instance);
|
||||
return del.Invoke(instance);
|
||||
}
|
||||
public static object? Execution(Func<object, object?[]?, object?> del, object instance, object?[]? parameters)
|
||||
{
|
||||
return del?.Invoke(instance, parameters);
|
||||
return del.Invoke(instance, parameters);
|
||||
}
|
||||
|
||||
|
||||
public static async Task<object?> ExecutionAsync(Func<object, Task> del, object instance)
|
||||
{
|
||||
await del.Invoke(instance);
|
||||
return null;
|
||||
}
|
||||
public static async Task<object?> ExecutionAsync(Func<object, object?[]?, Task> del, object instance, object?[]? parameters)
|
||||
{
|
||||
await del.Invoke(instance, parameters);
|
||||
return null;
|
||||
}
|
||||
public static async Task<object?> ExecutionAsync(Func<object, Task<object?>> del, object instance)
|
||||
{
|
||||
return await del.Invoke(instance);
|
||||
}
|
||||
public static async Task<object?> ExecutionAsync(Func<object, object?[]?, Task<object?>> del, object instance, object?[]? parameters)
|
||||
{
|
||||
return await del.Invoke(instance, parameters);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -146,9 +146,26 @@ namespace Serein.NodeFlow.Tool
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "instance");
|
||||
var methodCall = Expression.Call(Expression.Convert(parameter, type), methodInfo);
|
||||
var lambda = Expression.Lambda(Expression.Convert(methodCall, typeof(object)), parameter);
|
||||
// Func<object, object>
|
||||
return lambda.Compile();
|
||||
|
||||
if(MethodDetailsHelperTmp.IsGenericTask(methodInfo.ReturnType,out var taskResult))
|
||||
{
|
||||
if(taskResult is null)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, Task>>(Expression.Convert(methodCall, typeof(Task)), parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, Task<object>>>(Expression.Convert(methodCall, typeof(Task<object>)), parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, object>>(Expression.Convert(methodCall, typeof(object)), parameter);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -262,14 +279,29 @@ namespace Serein.NodeFlow.Tool
|
||||
convertedArgs
|
||||
);
|
||||
|
||||
// 创建 lambda 表达式
|
||||
var lambda = Expression.Lambda<Func<object, object[], object>>(
|
||||
Expression.Convert(methodCall, typeof(object)),
|
||||
instanceParam,
|
||||
argsParam
|
||||
);
|
||||
//var resule = task.DynamicInvoke((object)[Activator.CreateInstance(type), [new DynamicContext(null)]]);
|
||||
return lambda.Compile();
|
||||
if (MethodDetailsHelperTmp.IsGenericTask(methodInfo.ReturnType, out var taskResult))
|
||||
{
|
||||
if (taskResult is null)
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, object[], Task>>
|
||||
(Expression.Convert(methodCall, typeof(Task)), instanceParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, object[], Task<object>>>
|
||||
(Expression.Convert(methodCall, typeof(Task<object>)), instanceParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var lambda = Expression.Lambda<Func<object, object[], object>>
|
||||
(Expression.Convert(methodCall, typeof(object)), instanceParam, argsParam);
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ public static class MethodDetailsHelperTmp
|
||||
method.ReturnType);// 返回值
|
||||
|
||||
|
||||
Type returnType;
|
||||
Type? returnType;
|
||||
bool isTask = IsGenericTask(method.ReturnType, out var taskResult);
|
||||
|
||||
if (attribute.MethodDynamicType == Library.Enums.NodeType.Flipflop)
|
||||
@@ -85,7 +85,7 @@ public static class MethodDetailsHelperTmp
|
||||
}
|
||||
else if(isTask)
|
||||
{
|
||||
returnType = taskResult;
|
||||
returnType = taskResult is null ? typeof(Task) : taskResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -119,12 +119,12 @@ public static class MethodDetailsHelperTmp
|
||||
|
||||
}
|
||||
|
||||
public static bool IsGenericTask(Type returnType, out Type taskResult)
|
||||
public static bool IsGenericTask(Type returnType, out Type? taskResult)
|
||||
{
|
||||
// 判断是否为 Task 类型或泛型 Task<T>
|
||||
if (returnType == typeof(Task))
|
||||
{
|
||||
taskResult = returnType;
|
||||
taskResult = null;
|
||||
return true;
|
||||
}
|
||||
else if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(Task<>))
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace Serein.WorkBench
|
||||
public App()
|
||||
{
|
||||
|
||||
|
||||
#if false //测试 操作表达式,条件表达式
|
||||
#region 测试数据
|
||||
string expression = "";
|
||||
|
||||
Reference in New Issue
Block a user