From 9941f24c5ddfda6d6260116756ef06149d54658e Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Thu, 20 Mar 2025 22:54:10 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E3=80=82=E4=B8=8A=E4=B8=8B=E6=96=87=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E5=AF=B9=E8=B1=A1=E6=B1=A0=E5=B0=81=E8=A3=85=EF=BC=8C?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=96=B9=E6=B3=95=E8=B0=83=E7=94=A8=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E4=BC=A0=E9=80=92CancellationTokenSource=E7=94=A8?= =?UTF-8?q?=E6=9D=A5=E4=B8=AD=E6=AD=A2=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library/Api/IDynamicContext.cs | 11 +- Library/Api/IFlowEnvironment.cs | 2 +- Library/Api/ISereinIoc.cs | 4 +- Library/FlowNode/ContainerFlowEnvironment.cs | 16 +- Library/FlowNode/DynamicContext.cs | 42 +- Library/FlowNode/MethodDetails.cs | 24 +- Library/FlowNode/NodeModelBaseFunc.cs | 93 ++-- Library/NodeStaticConfig.cs | 10 - Library/Utils/ObjectPool.cs | 160 +++++++ NodeFlow/Env/FlowEnvironment.cs | 100 +++-- NodeFlow/Env/FlowEnvironmentDecorator.cs | 18 +- NodeFlow/Env/RemoteFlowEnvironment.cs | 4 +- .../{FlowFunc.cs => FlowNodeExtension.cs} | 33 +- NodeFlow/FlowStarter.cs | 413 ------------------ NodeFlow/FlowTaskLibrary.cs | 52 +++ NodeFlow/FlowWorkManagement.cs | 386 ++++++++++++++++ NodeFlow/Model/CompositeConditionNode.cs | 8 +- NodeFlow/Model/SingleConditionNode.cs | 3 +- NodeFlow/Model/SingleExpOpNode.cs | 7 +- NodeFlow/Model/SingleFlipflopNode.cs | 14 +- NodeFlow/Model/SingleGlobalDataNode.cs | 3 +- NodeFlow/Model/SingleScriptNode.cs | 13 +- NodeFlow/Model/SingleUINode.cs | 7 +- Workbench/App.xaml | 4 +- Workbench/App.xaml.cs | 16 +- .../ViewModel/ScriptNodeControlViewModel.cs | 4 +- .../Node/ViewModel/UINodeControlViewModel.cs | 4 +- 27 files changed, 830 insertions(+), 621 deletions(-) create mode 100644 Library/Utils/ObjectPool.cs rename NodeFlow/{FlowFunc.cs => FlowNodeExtension.cs} (77%) delete mode 100644 NodeFlow/FlowStarter.cs create mode 100644 NodeFlow/FlowTaskLibrary.cs create mode 100644 NodeFlow/FlowWorkManagement.cs diff --git a/Library/Api/IDynamicContext.cs b/Library/Api/IDynamicContext.cs index 9add8da..db5b973 100644 --- a/Library/Api/IDynamicContext.cs +++ b/Library/Api/IDynamicContext.cs @@ -26,12 +26,6 @@ namespace Serein.Library.Api /// RunState RunState { get; } - /// - /// 用来在当前流程上下文间传递数据 - /// - //Dictionary ContextShareData { get; } - - object Tag { get; set; } /// /// 下一个要执行的节点类别 @@ -77,6 +71,11 @@ namespace Serein.Library.Api /// void AddOrUpdate(string nodeGuid, object flowData); + /// + /// 重置流程状态(用于对象池回收) + /// + void Reset(); + /// /// 用以提前结束当前上下文流程的运行 /// diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index 8b491bd..4cf05e8 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -693,7 +693,7 @@ namespace Serein.Library.Api /// /// 全局触发器运行状态 /// - RunState FlipFlopState { get; set; } + //RunState FlipFlopState { get; set; } /// /// 表示当前环境 diff --git a/Library/Api/ISereinIoc.cs b/Library/Api/ISereinIoc.cs index 5516b4b..f8f181a 100644 --- a/Library/Api/ISereinIoc.cs +++ b/Library/Api/ISereinIoc.cs @@ -49,7 +49,7 @@ namespace Serein.Library.Api /// 登记使用的名称 /// 实例对象 /// 是否注册成功 - bool RegisterInstance(string key, object instance); + /// bool RegisterInstance(string key, object instance); /// /// 获取类型的实例。如果需要获取的类型以“接口-实现类”的方式注册,请使用接口的类型。 @@ -67,7 +67,7 @@ namespace Serein.Library.Api /// /// 登记实例时使用的Key /// - T Get(string key); + /// T Get(string key); diff --git a/Library/FlowNode/ContainerFlowEnvironment.cs b/Library/FlowNode/ContainerFlowEnvironment.cs index 97b6aff..1677008 100644 --- a/Library/FlowNode/ContainerFlowEnvironment.cs +++ b/Library/FlowNode/ContainerFlowEnvironment.cs @@ -295,10 +295,10 @@ namespace Serein.Library { return (T)sereinIOC.Get(typeof(T)); } - T ISereinIOC.Get(string key) - { - return sereinIOC.Get(key); - } + //T ISereinIOC.Get(string key) + //{ + // return sereinIOC.Get(key); + //} bool ISereinIOC.RegisterPersistennceInstance(string key, object instance) @@ -311,10 +311,10 @@ namespace Serein.Library return sereinIOC.RegisterPersistennceInstance(key, instance); } - bool ISereinIOC.RegisterInstance(string key, object instance) - { - return sereinIOC.RegisterInstance(key, instance); - } + //bool ISereinIOC.RegisterInstance(string key, object instance) + //{ + // return sereinIOC.RegisterInstance(key, instance); + //} object ISereinIOC.Instantiate(Type type) diff --git a/Library/FlowNode/DynamicContext.cs b/Library/FlowNode/DynamicContext.cs index 09ff1e0..a1b6bc6 100644 --- a/Library/FlowNode/DynamicContext.cs +++ b/Library/FlowNode/DynamicContext.cs @@ -22,7 +22,7 @@ namespace Serein.Library RunState = RunState.Running; } - private readonly string _guid = global::System.Guid.NewGuid().ToString(); + private string _guid = global::System.Guid.NewGuid().ToString(); string IDynamicContext.Guid => _guid; /// @@ -35,11 +35,6 @@ namespace Serein.Library /// public RunState RunState { get; set; } = RunState.NoStart; - /// - /// 用来在当前流程上下文间传递数据 - /// - //public Dictionary ContextShareData { get; } = new Dictionary(); - public object Tag { get; set; } /// /// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。 @@ -133,6 +128,37 @@ namespace Serein.Library return null; } + /// + /// 重置 + /// + public void Reset() + { + //foreach (var nodeObj in dictNodeFlowData.Values) + //{ + // if (nodeObj is null) + // { + + // } + // else + // { + // if (typeof(IDisposable).IsAssignableFrom(nodeObj?.GetType()) && nodeObj is IDisposable disposable) + // { + // disposable?.Dispose(); + // } + // } + //} + //if (Tag != null && typeof(IDisposable).IsAssignableFrom(Tag?.GetType()) && Tag is IDisposable tagDisposable) + //{ + // tagDisposable?.Dispose(); + //} + this.dictNodeFlowData?.Clear(); + ExceptionOfRuning = null; + NextOrientation = ConnectionInvokeType.None; + RunState = RunState.Running; + _guid = global::System.Guid.NewGuid().ToString(); + } + + /// /// 结束当前流程上下文 /// @@ -156,9 +182,11 @@ namespace Serein.Library //{ // tagDisposable?.Dispose(); //} - this.Tag = null; this.dictNodeFlowData?.Clear(); + ExceptionOfRuning = null; + NextOrientation = ConnectionInvokeType.None; RunState = RunState.Completion; + _guid = global::System.Guid.NewGuid().ToString(); } private void Dispose(ref IDictionary keyValuePairs) diff --git a/Library/FlowNode/MethodDetails.cs b/Library/FlowNode/MethodDetails.cs index c4916a5..2c37b47 100644 --- a/Library/FlowNode/MethodDetails.cs +++ b/Library/FlowNode/MethodDetails.cs @@ -45,8 +45,8 @@ namespace Serein.Library /// /// 作用实例(多个相同的节点将会共享同一个实例) /// - [PropertyInfo] - private object _actingInstance; + // [PropertyInfo] + // private object _actingInstance; /// /// 方法名称 @@ -237,16 +237,16 @@ namespace Serein.Library // this => 是元数据 var md = new MethodDetails( nodeModel) // 创建新节点时拷贝实例 { - AssemblyName = this.AssemblyName, - ActingInstance = this.ActingInstance, - ActingInstanceType = this.ActingInstanceType, - MethodDynamicType = this.MethodDynamicType, - MethodAnotherName = this.MethodAnotherName, - ReturnType = this.ReturnType, - MethodName = this.MethodName, - MethodLockName = this.MethodLockName, - IsProtectionParameter = this.IsProtectionParameter, - ParamsArgIndex = this.ParamsArgIndex, + AssemblyName = this.AssemblyName, // 拷贝 + //ActingInstance = this.ActingInstance, + ActingInstanceType = this.ActingInstanceType, // 拷贝 + MethodDynamicType = this.MethodDynamicType, // 拷贝 + MethodAnotherName = this.MethodAnotherName, // 拷贝 + ReturnType = this.ReturnType, // 拷贝 + MethodName = this.MethodName, // 拷贝 + MethodLockName = this.MethodLockName, // 拷贝 + IsProtectionParameter = this.IsProtectionParameter, // 拷贝 + ParamsArgIndex = this.ParamsArgIndex, // 拷贝 ParameterDetailss = this.ParameterDetailss?.Select(p => p?.CloneOfModel(nodeModel)).ToArray(), // 拷贝属于节点方法的新入参描述 }; diff --git a/Library/FlowNode/NodeModelBaseFunc.cs b/Library/FlowNode/NodeModelBaseFunc.cs index bd2a1d8..c836087 100644 --- a/Library/FlowNode/NodeModelBaseFunc.cs +++ b/Library/FlowNode/NodeModelBaseFunc.cs @@ -81,7 +81,7 @@ namespace Serein.Library } this.MethodDetails.ParameterDetailss = null; - this.MethodDetails.ActingInstance = null; + //this.MethodDetails.ActingInstance = null; this.MethodDetails.NodeModel = null; this.MethodDetails.ReturnType = null; this.MethodDetails.AssemblyName = null; @@ -249,43 +249,46 @@ namespace Serein.Library /// /// /// - public static bool IsBradk(IDynamicContext context, CancellationTokenSource flowCts) - { - // 上下文不再执行 - if (context.RunState == RunState.Completion) - { - return true; - } + //public static bool IsBradk(IDynamicContext context) + //{ + // // 上下文不再执行 + // if (context.RunState == RunState.Completion) + // { + // return true; + // } - // 不存在全局触发器时,流程运行状态被设置为完成,退出执行,用于打断无限循环分支。 - if (flowCts is null && context.Env.FlowState == RunState.Completion) - { - return true; - } + // // 不存在全局触发器时,流程运行状态被设置为完成,退出执行,用于打断无限循环分支。 + // if (flowCts is null && context.Env.FlowState == RunState.Completion) + // { + // return true; + // } - // 如果存在全局触发器,且触发器的执行任务已经被取消时,退出执行。 - if (flowCts != null) - { - if (flowCts.IsCancellationRequested) - return true; - } - return false; - } + // // 如果存在全局触发器,且触发器的执行任务已经被取消时,退出执行。 + // if (flowCts != null) + // { + // if (flowCts.IsCancellationRequested) + // return true; + // } + // return false; + //} /// /// 开始执行 /// /// + /// 流程运行 /// - public async Task StartFlowAsync(IDynamicContext context) + public async Task StartFlowAsync(IDynamicContext context, CancellationToken token) { Stack stack = new Stack(); HashSet processedNodes = new HashSet(); // 用于记录已处理上游节点的节点 stack.Push(this); - var flowCts = context.Env.IOC.Get(NodeStaticConfig.FlipFlopCtsName); - bool hasFlipflow = flowCts != null; - while (stack.Count > 0) // 循环中直到栈为空才会退出循环 + while (context.RunState != RunState.Completion // 没有完成 + && token.IsCancellationRequested == false // 没有取消 + && stack.Count > 0) // 循环中直到栈为空才会退出循环 { + + #if DEBUG await Task.Delay(1); #endif @@ -299,15 +302,12 @@ namespace Serein.Library object newFlowData; try { + newFlowData = await currentNode.ExecutingAsync(context, token); - if (IsBradk(context, flowCts)) break; // 退出执行 - newFlowData = await currentNode.ExecutingAsync(context); - if (IsBradk(context, flowCts)) break; // 退出执行 if (context.NextOrientation == ConnectionInvokeType.None) // 没有手动设置时,进行自动设置 { context.NextOrientation = ConnectionInvokeType.IsSucceed; } - } catch (Exception ex) { @@ -316,9 +316,7 @@ namespace Serein.Library context.NextOrientation = ConnectionInvokeType.IsError; context.ExceptionOfRuning = ex; } - - - await RefreshFlowDataAndExpInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据 + context.AddOrUpdate(currentNode.Guid, newFlowData); // 上下文中更新数据 #endregion #region 执行完成 @@ -355,14 +353,10 @@ namespace Serein.Library /// /// 流程上下文 /// 节点传回数据对象 - public virtual async Task ExecutingAsync(IDynamicContext context) + public virtual async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { #region 调试中断 - if(context.NextOrientation == ConnectionInvokeType.IsError) - { - } - // 执行触发检查是否需要中断 if (DebugSetting.IsInterrupt) { @@ -370,8 +364,8 @@ namespace Serein.Library await DebugSetting.GetInterruptTask.Invoke(); //await fit.WaitTriggerAsync(Guid); // 创建一个等待的中断任务 SereinEnv.WriteLine(InfoType.INFO, $"[{this.MethodDetails?.MethodName}]中断已取消,开始执行后继分支"); - var flowCts = context.Env.IOC.Get(NodeStaticConfig.FlipFlopCtsName); - if (IsBradk(context, flowCts)) return null; // 流程已终止,取消后续的执行 + //var flowCts = context.Env.IOC.Get(NodeStaticConfig.FlipFlopCtsName); + if (token.IsCancellationRequested) { return null; } } #endregion @@ -385,17 +379,14 @@ namespace Serein.Library { throw new Exception($"节点{this.Guid}不存在对应委托"); } - if (md.ActingInstance is null) + var instance = Env.IOC.Get(md.ActingInstanceType); + if(instance == null) { - md.ActingInstance = context.Env.IOC.Get(md.ActingInstanceType); - if (md.ActingInstance is null) - { - md.ActingInstance = context.Env.IOC.Instantiate(md.ActingInstanceType); - } + Env.IOC.Register(md.ActingInstanceType).Build(); + instance = Env.IOC.Get(md.ActingInstanceType); } - - object[] args = await GetParametersAsync(context); - var result = await dd.InvokeAsync(md.ActingInstance, args); + object[] args = await GetParametersAsync(context, token); + var result = await dd.InvokeAsync(instance, args); return result; } @@ -403,7 +394,7 @@ namespace Serein.Library /// /// 获取对应的参数数组 /// - public async Task GetParametersAsync(IDynamicContext context) + public async Task GetParametersAsync(IDynamicContext context, CancellationToken token) { if (MethodDetails.ParameterDetailss.Length == 0) { @@ -454,13 +445,13 @@ namespace Serein.Library /// - /// 更新节点数据,并检查监视表达式是否生效 + /// 检查监视表达式是否生效 /// /// 上下文 /// 节点Moel /// 新的数据 /// - public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object newData = null) + public static async Task CheckExpInterrupt(IDynamicContext context, NodeModelBase nodeModel, object newData = null) { string guid = nodeModel.Guid; context.AddOrUpdate(guid, newData); // 上下文中更新数据 diff --git a/Library/NodeStaticConfig.cs b/Library/NodeStaticConfig.cs index 8fff928..6931234 100644 --- a/Library/NodeStaticConfig.cs +++ b/Library/NodeStaticConfig.cs @@ -9,16 +9,6 @@ namespace Serein.Library { public static class NodeStaticConfig { - /// - /// 全局触发器CTS - /// - public const string FlipFlopCtsName = "$FlowFlipFlopCts"; - /// - /// 流程运行CTS - /// - public const string FlowRungCtsName = "$FlowRungCtsName"; - - /// /// 节点的命名空间 /// diff --git a/Library/Utils/ObjectPool.cs b/Library/Utils/ObjectPool.cs new file mode 100644 index 0000000..6e61be8 --- /dev/null +++ b/Library/Utils/ObjectPool.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Serein.Library.Utils +{ + /// + /// 具有预定义池大小限制的对象池模式的通用实现。其主要目的是将有限数量的经常使用的对象保留在池中,以便进一步回收。 + /// + /// 注: + /// 1)目标不是保留所有返回的对象。池不是用来存储的。如果池中没有空间,则会丢弃额外返回的对象。 + /// + /// 2)这意味着如果对象是从池中获得的,调用者将在相对较短的时间内返回它 + /// 时间。长时间保持检出对象是可以的,但是会降低池的有用性。你只需要重新开始。 + /// + /// 不将对象返回给池并不会损害池的工作,但这是一种不好的做法。 + /// 基本原理:如果没有重用对象的意图,就不要使用pool——只使用“new” + /// + public class ObjectPool where T : class + { + [DebuggerDisplay("{Value,nq}")] + private struct Element + { + internal T Value; + } + + // 不使用System。Func{T},因为. net 2.0没有该类型。 + public delegate T Factory(); + + // 池对象的存储。第一个项存储在专用字段中,因为我们希望能够满足来自它的大多数请求。 + private T _firstItem; + + private readonly Element[] _items; + + // 工厂在池的生命周期内被存储。只有当池需要扩展时,我们才调用它。 + // 与“new T()”相比,Func为实现者提供了更多的灵活性,并且比“new T()”更快。 + private readonly Factory _factory; + + public ObjectPool(Factory factory) + : this(factory, Environment.ProcessorCount * 2) + { } + + public ObjectPool(Factory factory, int size) + { + Debug.Assert(size >= 1); + _factory = factory; + _items = new Element[size - 1]; + } + + private T CreateInstance() + { + T inst = _factory(); + return inst; + } + + /// + /// 生成实例。 + /// + /// + /// 搜索策略是一种简单的线性探测,选择它是为了缓存友好。 + /// 请注意,Free会尝试将回收的对象存储在靠近起点的地方,从而在统计上减少我们通常搜索的距离。 + /// + public T Allocate() + { + /* + * PERF:检查第一个元素。如果失败,AllocateSlow将查看剩余的元素。 + * 注意,初始读是乐观地不同步的。 + * 这是有意为之。只有了待使用对象,我们才会返回。 + * 在最坏的情况下,我们可能会错过一些最近返回的对象。没什么大不了的。 + */ + T inst = _firstItem; + if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) + { + inst = AllocateSlow(); + } + + return inst; + } + + private T AllocateSlow() + { + Element[] items = _items; + + for (int i = 0; i < items.Length; i++) + { + // 注意,初始读是乐观地不同步的。这是有意为之。只有有了候选人,我们才会联系。在最坏的情况下,我们可能会错过一些最近返回的对象。没什么大不了的。 + T inst = items[i].Value; + if (inst != null) + { + if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) + { + return inst; + } + } + } + + return CreateInstance(); + } + + /// + ///返回对象到池。 + /// + /// + /// 搜索策略是一种简单的线性探测,选择它是因为它具有缓存友好性。 + /// 请注意Free会尝试将回收的对象存储在靠近起点的地方,从而在统计上减少我们通常在Allocate中搜索的距离。 + /// + public void Free(T obj) + { + Validate(obj); + + if (_firstItem == null) + { + // 这里故意不使用联锁。在最坏的情况下,两个对象可能存储在同一个槽中。这是不太可能发生的,只意味着其中一个对象将被收集。 + _firstItem = obj; + } + else + { + FreeSlow(obj); + } + } + + private void FreeSlow(T obj) + { + Element[] items = _items; + for (int i = 0; i < items.Length; i++) + { + if (items[i].Value == null) + { + // 这里故意不使用联锁。在最坏的情况下,两个对象可能存储在同一个槽中。这是不太可能发生的,只意味着其中一个对象将被收集。 + items[i].Value = obj; + break; + } + } + } + + [Conditional("DEBUG")] + private void Validate(object obj) + { + Debug.Assert(obj != null, "freeing null?"); + + Debug.Assert(_firstItem != obj, "freeing twice?"); + + var items = _items; + for (int i = 0; i < items.Length; i++) + { + var value = items[i].Value; + if (value == null) + { + return; + } + + Debug.Assert(value != obj, "freeing twice?"); + } + } + } +} diff --git a/NodeFlow/Env/FlowEnvironment.cs b/NodeFlow/Env/FlowEnvironment.cs index 0b07d23..4c0787e 100644 --- a/NodeFlow/Env/FlowEnvironment.cs +++ b/NodeFlow/Env/FlowEnvironment.cs @@ -4,6 +4,7 @@ using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; using Serein.NodeFlow.Model; using Serein.NodeFlow.Tool; +using System; using System.Reactive; using System.Reflection; using System.Text; @@ -32,7 +33,7 @@ namespace Serein.NodeFlow.Env { this.sereinIOC = new SereinIOC(); this.IsGlobalInterrupt = false; - this.flowStarter = null; + this.flowTaskManagement = null; this.sereinIOC.OnIOCMembersChanged += e => { if (OperatingSystem.IsWindows()) @@ -357,9 +358,9 @@ namespace Serein.NodeFlow.Env } /// - /// 流程启动器(每次运行时都会重新new一个) + /// 流程任务管理 /// - private FlowStarter? flowStarter; + private FlowWorkManagement? flowTaskManagement; #endregion @@ -381,29 +382,44 @@ namespace Serein.NodeFlow.Env } + /// /// 异步运行 /// /// public async Task StartFlowAsync() { - flowStarter ??= new FlowStarter(); - var nodes = NodeModels.Values.ToList(); - - List initMethods = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init); - List loadMethods = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Loading); - List exitMethods = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Exit); - Dictionary> autoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(); - IOC.Reset(); - await flowStarter.RunAsync(this, nodes, autoRegisterTypes, initMethods, loadMethods, exitMethods); - //_ = Task.Run(async () => - //{ - // //if (FlipFlopState == RunState.Completion) - // //{ - // // await ExitFlowAsync(); // 未运行触发器时,才会调用结束方法 - // //} - //}); + IOC.Register(); // 注册脚本接口 + + var flowTaskOptions = new FlowTaskLibrary + { + + Environment = this, + FlowContextPool = new ObjectPool(() => new DynamicContext(this)), + Nodes = NodeModels.Values.ToList(), + AutoRegisterTypes = this.FlowLibraryManagement.GetaAutoRegisterType(), + InitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Init), + LoadMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Loading), + ExitMds = this.FlowLibraryManagement.GetMdsOnFlowStart(NodeType.Exit), + + }; + flowTaskManagement = new FlowWorkManagement(flowTaskOptions); + var cts = new CancellationTokenSource(); + try + { + var t =await flowTaskManagement.RunAsync(cts.Token); + } + catch (Exception ex) + { + SereinEnv.WriteLine(ex); + } + finally + { + + SereinEnv.WriteLine(InfoType.INFO, $"流程运行完毕{Environment.NewLine}"); ; + } + flowTaskOptions = null; return true; @@ -417,7 +433,7 @@ namespace Serein.NodeFlow.Env public async Task StartAsyncInSelectNode(string startNodeGuid) { - if (flowStarter is null) + if (flowTaskManagement is null) { SereinEnv.WriteLine(InfoType.ERROR, "没有启动流程,无法运行单个节点"); return false; @@ -435,7 +451,7 @@ namespace Serein.NodeFlow.Env //SerinExpressionEvaluator.Evaluate(setExp, nodeModel,out _); //var getExpResult2 = SerinExpressionEvaluator.Evaluate(getExp, nodeModel, out _); - await flowStarter.StartFlowInSelectNodeAsync(this, nodeModel); + await flowTaskManagement.StartFlowInSelectNodeAsync(this, nodeModel); return true; } else @@ -454,7 +470,9 @@ namespace Serein.NodeFlow.Env object result = new Unit(); if (this.NodeModels.TryGetValue(nodeGuid, out var model)) { - result = await model.ExecutingAsync(context); + CancellationTokenSource cts = new CancellationTokenSource(); + result = await model.ExecutingAsync(context, cts.Token); + cts?.Cancel(); } return result; } @@ -464,10 +482,10 @@ namespace Serein.NodeFlow.Env /// public Task ExitFlowAsync() { - flowStarter?.Exit(); + flowTaskManagement?.Exit(); UIContextOperation?.Invoke(() => OnFlowRunComplete?.Invoke(new FlowEventArgs())); IOC.Reset(); - flowStarter = null; + flowTaskManagement = null; GC.Collect(); return Task.FromResult(true); } @@ -480,12 +498,12 @@ namespace Serein.NodeFlow.Env { var nodeModel = GuidToModel(nodeGuid); if (nodeModel is null) return; - if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 + if (flowTaskManagement is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 { if (FlowState != RunState.Completion && flipflopNode.NotExitPreviousNode()) // 正在运行,且该触发器没有上游节点 { - _ = flowStarter.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 + _ = flowTaskManagement.RunGlobalFlipflopAsync(this, flipflopNode);// 被父节点移除连接关系的子节点若为触发器,且无上级节点,则当前流程正在运行,则加载到运行环境中 } } @@ -499,9 +517,9 @@ namespace Serein.NodeFlow.Env { var nodeModel = GuidToModel(nodeGuid); if (nodeModel is null) return; - if (flowStarter is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 + if (flowTaskManagement is not null && nodeModel is SingleFlipflopNode flipflopNode) // 子节点为触发器 { - flowStarter.TerminateGlobalFlipflopRuning(flipflopNode); + flowTaskManagement.TerminateGlobalFlipflopRuning(flipflopNode); } } @@ -793,7 +811,7 @@ namespace Serein.NodeFlow.Env } #endregion - var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 + var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 if (nodeModel is null) { nodeInfo.Guid = string.Empty; @@ -926,7 +944,7 @@ namespace Serein.NodeFlow.Env NodeModelBase? nodeModel; if (methodDetailsInfo is null) { - nodeModel = FlowFunc.CreateNode(this, nodeControlType); // 加载基础节点 + nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType); // 加载基础节点 } else { @@ -934,7 +952,7 @@ namespace Serein.NodeFlow.Env methodDetailsInfo.MethodName, out var methodDetails)) { - nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 一般的加载节点方法 + nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType, methodDetails); // 一般的加载节点方法 } else { @@ -1032,7 +1050,7 @@ namespace Serein.NodeFlow.Env if (remoteNode is SingleFlipflopNode flipflopNode) { - flowStarter?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被移除的是全局触发器,尝试从启动器移除 + flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被移除的是全局触发器,尝试从启动器移除 } remoteNode.Remove(); // 调用节点的移除方法 @@ -1689,7 +1707,7 @@ namespace Serein.NodeFlow.Env if (toNode is SingleFlipflopNode flipflopNode) { - flowStarter?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 + flowTaskManagement?.TerminateGlobalFlipflopRuning(flipflopNode); // 假设被连接的是全局触发器,尝试移除 } var isPass = false; @@ -1896,10 +1914,10 @@ namespace Serein.NodeFlow.Env { return (T)sereinIOC.Get(typeof(T)); } - T ISereinIOC.Get(string key) - { - return sereinIOC.Get(key); - } + //T ISereinIOC.Get(string key) + //{ + // return sereinIOC.Get(key); + //} bool ISereinIOC.RegisterPersistennceInstance(string key, object instance) @@ -1908,10 +1926,10 @@ namespace Serein.NodeFlow.Env return sereinIOC.RegisterPersistennceInstance(key, instance); } - bool ISereinIOC.RegisterInstance(string key, object instance) - { - return sereinIOC.RegisterInstance(key, instance); - } + //bool ISereinIOC.RegisterInstance(string key, object instance) + //{ + // return sereinIOC.RegisterInstance(key, instance); + //} object ISereinIOC.Instantiate(Type type) diff --git a/NodeFlow/Env/FlowEnvironmentDecorator.cs b/NodeFlow/Env/FlowEnvironmentDecorator.cs index 6fffc02..8cc8b48 100644 --- a/NodeFlow/Env/FlowEnvironmentDecorator.cs +++ b/NodeFlow/Env/FlowEnvironmentDecorator.cs @@ -102,7 +102,7 @@ namespace Serein.NodeFlow.Env /// public InfoClass InfoClass { get => currentFlowEnvironment.InfoClass; set => currentFlowEnvironment.InfoClass = value; } public RunState FlowState { get => currentFlowEnvironment.FlowState; set => currentFlowEnvironment.FlowState = value; } - public RunState FlipFlopState { get => currentFlowEnvironment.FlipFlopState; set => currentFlowEnvironment.FlipFlopState = value; } + //public RunState FlipFlopState { get => currentFlowEnvironment.FlipFlopState; set => currentFlowEnvironment.FlipFlopState = value; } public event LoadDllHandler OnDllLoad { add { currentFlowEnvironmentEvent.OnDllLoad += value; } @@ -607,10 +607,10 @@ namespace Serein.NodeFlow.Env return IOC.RegisterPersistennceInstance(key, instance); } - public bool RegisterInstance(string key, object instance) - { - return IOC.RegisterInstance(key, instance); - } + //public bool RegisterInstance(string key, object instance) + //{ + // return IOC.RegisterInstance(key, instance); + //} public object Get(Type type) { @@ -622,10 +622,10 @@ namespace Serein.NodeFlow.Env return IOC.Get(); } - public T Get(string key) - { - return IOC.Get(key); - } + //public T Get(string key) + //{ + // return IOC.Get(key); + //} public object Instantiate(Type type) { diff --git a/NodeFlow/Env/RemoteFlowEnvironment.cs b/NodeFlow/Env/RemoteFlowEnvironment.cs index ebadaf1..e0d63b9 100644 --- a/NodeFlow/Env/RemoteFlowEnvironment.cs +++ b/NodeFlow/Env/RemoteFlowEnvironment.cs @@ -784,7 +784,7 @@ namespace Serein.NodeFlow.Env } //MethodDetailss.TryGetValue(methodDetailsInfo.MethodName, out var methodDetails);// 加载项目时尝试获取方法信息 - var nodeModel = FlowFunc.CreateNode(this, nodeControlType, methodDetails); // 远程环境下加载节点 + var nodeModel = FlowNodeExtension.CreateNode(this, nodeControlType, methodDetails); // 远程环境下加载节点 nodeModel.LoadInfo(nodeInfo); TryAddNode(nodeModel); IsLoadingNode = false; @@ -1098,7 +1098,7 @@ namespace Serein.NodeFlow.Env } #endregion - var nodeModel = FlowFunc.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 + var nodeModel = FlowNodeExtension.CreateNode(this, controlType, methodDetails); // 加载项目时创建节点 if (nodeModel is null) { nodeInfo.Guid = string.Empty; diff --git a/NodeFlow/FlowFunc.cs b/NodeFlow/FlowNodeExtension.cs similarity index 77% rename from NodeFlow/FlowFunc.cs rename to NodeFlow/FlowNodeExtension.cs index 2609bca..dbf9c44 100644 --- a/NodeFlow/FlowFunc.cs +++ b/NodeFlow/FlowNodeExtension.cs @@ -12,7 +12,7 @@ namespace Serein.NodeFlow /// /// 流程环境需要的扩展方法 /// - public static class FlowFunc + public static class FlowNodeExtension { /// /// 判断是否为基础节点 @@ -71,37 +71,6 @@ namespace Serein.NodeFlow - ///// - ///// 从节点信息读取节点类型 - ///// - ///// - ///// - ///// - //public static NodeControlType GetNodeControlType(NodeInfo nodeInfo) - //{ - // if(!EnumHelper.TryConvertEnum(nodeInfo.Type, out var controlType)) - // { - // return NodeControlType.None; - // } - // return controlType; - // // 创建控件实例 - // //NodeControlType controlType = nodeInfo.Type switch - // //{ - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleActionNode)}" => NodeControlType.Action,// 动作节点控件 - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleFlipflopNode)}" => NodeControlType.Flipflop, // 触发器节点控件 - - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleConditionNode)}" => NodeControlType.ExpCondition,// 条件表达式控件 - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleExpOpNode)}" => NodeControlType.ExpOp, // 操作表达式控件 - - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(CompositeConditionNode)}" => NodeControlType.ConditionRegion, // 条件区域控件 - - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleGlobalDataNode)}" => NodeControlType.GlobalData, // 数据节点 - // // $"{NodeStaticConfig.NodeSpaceName}.{nameof(SingleScriptNode)}" => NodeControlType.Script, // 数据节点 - // // _ => NodeControlType.None, - // //}; - // //return controlType; - //} - /// /// 程序集封装依赖 /// diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs deleted file mode 100644 index 4f8f127..0000000 --- a/NodeFlow/FlowStarter.cs +++ /dev/null @@ -1,413 +0,0 @@ -using Serein.Library; -using Serein.Library.Api; -using Serein.Library.Utils; -using Serein.NodeFlow.Model; -using Serein.NodeFlow.Tool; -using System.Collections.Concurrent; - -namespace Serein.NodeFlow -{ - /// - /// 流程启动器 - /// - public class FlowStarter - { - /// - /// 控制所有全局触发器的结束 - /// - private CancellationTokenSource? _flipFlopCts; - - /// - /// 是否停止启动 - /// - private bool IsStopStart = false; - - /// - /// 结束运行时需要执行的方法 - /// - private Func? ExitAction { get; set; } - - /// - /// 从选定的节点开始运行 - /// - /// - /// - /// - public async Task StartFlowInSelectNodeAsync(IFlowEnvironment env, NodeModelBase startNode) - { - IDynamicContext context; - context = new Serein.Library.DynamicContext(env); // 从起始节点启动流程时创建上下文 - await startNode.StartFlowAsync(context); // 开始运行时从选定节点开始运行 - context.Exit(); - } - - - /// - /// 开始运行(需要准备好方法信息) - /// - /// 运行环境 - /// 环境中已加载的所有节点 - /// 初始化方法 - /// 加载时方法 - /// 结束时方法 - /// - public async Task RunAsync(IFlowEnvironment env, - List nodes, - Dictionary> autoRegisterTypes, - List initMethods, - List loadingMethods, - List exitMethods) - { - - #region 注册基本类 - env.IOC.Register(); // 注册脚本接口 - #endregion - - env.FlowState = RunState.Running; // 开始运行 - NodeModelBase? startNode = nodes.FirstOrDefault(node => node.IsStart); - if (startNode is null) { - env.FlowState = RunState.Completion; // 不存在起点,退出流程 - return; - } - - #region 获取所有触发器,以及已加载节点的方法信息 - List runNodeMd; - List flipflopNodes; - - flipflopNodes = nodes.Where(it => it.MethodDetails?.MethodDynamicType == NodeType.Flipflop && it.IsStart == false) - .Select(it => (SingleFlipflopNode)it) - .Where(node => node.DebugSetting.IsEnable && node is SingleFlipflopNode flipflopNode && flipflopNode.NotExitPreviousNode()) - .ToList();// 获取需要再运行开始之前启动的触发器节点 - runNodeMd = nodes.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息 - - - #endregion - - #region 选择运行环境的上下文 - - // 判断使用哪一种流程上下文 - IDynamicContext Context = new Serein.Library.DynamicContext(env); // 从起始节点启动流程时创建上下文 - #endregion - - #region 初始化运行环境的Ioc容器 - - // 清除节点使用的对象,筛选出需要初始化的方法描述 - var thisRuningMds = new List(); - thisRuningMds.AddRange(runNodeMd.Where(md => md?.ActingInstanceType is not null)); - thisRuningMds.AddRange(initMethods.Where(md => md?.ActingInstanceType is not null)); - thisRuningMds.AddRange(loadingMethods.Where(md => md?.ActingInstanceType is not null)); - thisRuningMds.AddRange(exitMethods.Where(md => md?.ActingInstanceType is not null)); - - - foreach (var nodeMd in thisRuningMds) - { - nodeMd.ActingInstance = null; - } - - // 初始化ioc容器中的类型对象 - foreach (var md in thisRuningMds) - { - if (md.ActingInstanceType != null) - { - env.IOC.Register(md.ActingInstanceType); - } - else - { - await Console.Out.WriteLineAsync($"{md.MethodName} - 没有类型声明"); - IsStopStart = true; - } - } - - if (IsStopStart) return;// 检查所有dll节点是否存在类型 - - env.IOC.Build(); // 流程启动前的初始化 - - foreach (var md in thisRuningMds) - { - md.ActingInstance = env.IOC.Get(md.ActingInstanceType); - if(md.ActingInstance is null) - { - await Console.Out.WriteLineAsync($"{md.MethodName} - 无法获取类型[{md.ActingInstanceType}]的实例"); - IsStopStart = true; - } - } - if (IsStopStart) - { - return;// 调用节点初始化后检查状态 - } - - - #endregion - - #region 执行初始化,绑定IOC容器,再执行加载时 - - if (autoRegisterTypes.TryGetValue(RegisterSequence.FlowInit, out var flowInitTypes)) - { - foreach (var type in flowInitTypes) - { - env.IOC.Register(type); // 初始化前注册 - } - } - Context.Env.IOC.Build(); // 绑定初始化时注册的类型 - //object?[]? args = [Context]; - foreach (var md in initMethods) // 初始化 - { - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 - { - throw new Exception("不存在对应委托"); - } - await dd.InvokeAsync(md.ActingInstance, [Context]); - //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); - } - Context.Env.IOC.Build(); // 绑定初始化时注册的类型 - - if(autoRegisterTypes.TryGetValue(RegisterSequence.FlowLoading,out var flowLoadingTypes)) - { - foreach (var type in flowLoadingTypes) - { - env.IOC.Register(type); // 初始化前注册 - } - } - Context.Env.IOC.Build(); // 绑定初始化时注册的类型 - foreach (var md in loadingMethods) // 加载 - { - //object?[]? data = [md.ActingInstance, args]; - //md.MethodDelegate.DynamicInvoke(data); - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行正在加载 - { - throw new Exception("不存在对应委托"); - } - await dd.InvokeAsync(md.ActingInstance, [Context]); - //((Action)del).Invoke(md.ActingInstance, [Context]); - //((Func)dd.EmitDelegate).Invoke(md.ActingInstance, [Context]); - } - Context.Env.IOC.Build(); // 预防有人在加载时才注册类型,再绑定一次 - #endregion - - #region 设置流程退出时的回调函数 - ExitAction = async () => - { - //env.IOC.Run(web => { - // web?.Stop(); - //}); - //env.IOC.Run(server => { - // server?.Stop(); - //}); - - foreach (MethodDetails? md in exitMethods) - { - if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行退出执行 - { - throw new Exception("不存在对应委托"); - } - await dd.InvokeAsync(md.ActingInstance, [Context]); - } - - if (_flipFlopCts != null && !_flipFlopCts.IsCancellationRequested) - { - _flipFlopCts?.Cancel(); - _flipFlopCts?.Dispose(); - } // 通知所有流程上下文停止运行 - TerminateAllGlobalFlipflop(); // 确保所有触发器不再运行 - SereinEnv.ClearFlowGlobalData(); // 清空全局数据缓存 - NativeDllHelper.FreeLibrarys(); // 卸载所有已加载的 Native Dll - env.IOC.Run(fit => fit.CancelAllTrigger());// 取消所有中断 - env.FlowState = RunState.Completion; - env.FlipFlopState = RunState.Completion; - - }; - #endregion - - #region 开始启动流程 - - try - { - //await TestScript(env); - await startNode.StartFlowAsync(Context); // 开始运行时从起始节点开始运行 - - if (flipflopNodes.Count > 0) - { - env.FlipFlopState = RunState.Running; - // 如果存在需要启动的触发器,则开始启动 - _flipFlopCts = new CancellationTokenSource(); - env.IOC.RegisterInstance(NodeStaticConfig.FlipFlopCtsName, _flipFlopCts); - - // 使用 TaskCompletionSource 创建未启动的触发器任务 - var tasks = flipflopNodes.Select(async node => - { - await RunGlobalFlipflopAsync(env,node); // 启动流程时启动全局触发器 - }).ToArray(); - _ = Task.WhenAll(tasks); - } - - - // 等待结束 - if(env.FlipFlopState == RunState.Running && _flipFlopCts is not null) - { - while (!_flipFlopCts.IsCancellationRequested) - { - await Task.Delay(100); - } - } - } - catch (Exception ex) - { - await Console.Out.WriteLineAsync(ex.ToString()); - } - finally - { - env.FlowState = RunState.Completion; - SereinEnv.WriteLine(InfoType.INFO, $"流程运行完毕{Environment.NewLine}");; - } - #endregion - } - - private ConcurrentDictionary dictGlobalFlipflop = []; - - /// - /// 尝试添加全局触发器 - /// - /// - /// - public async Task RunGlobalFlipflopAsync(IFlowEnvironment env, SingleFlipflopNode singleFlipFlopNode) - { - if (dictGlobalFlipflop.TryAdd(singleFlipFlopNode, new CancellationTokenSource())) - { - singleFlipFlopNode.MethodDetails.ActingInstance ??= env.IOC.Get(singleFlipFlopNode.MethodDetails.ActingInstanceType); - await FlipflopExecuteAsync(env, singleFlipFlopNode, dictGlobalFlipflop[singleFlipFlopNode]); - } - } - - /// - /// 尝试移除全局触发器 - /// - /// - public void TerminateGlobalFlipflopRuning(SingleFlipflopNode singleFlipFlopNode) - { - if (dictGlobalFlipflop.TryRemove(singleFlipFlopNode, out var cts)) - { - if (!cts.IsCancellationRequested) - { - cts.Cancel(); - } - cts.Dispose(); - } - } - - /// - /// 终结所有全局触发器 - /// - private void TerminateAllGlobalFlipflop() - { - foreach ((var node, var cts) in dictGlobalFlipflop) - { - if (!cts.IsCancellationRequested) - { - cts.Cancel(); - } - cts.Dispose(); - } - dictGlobalFlipflop.Clear(); - } - - /// - /// 启动全局触发器 - /// - /// 流程运行全局环境 - /// 需要全局监听信号的触发器 - /// - private async Task FlipflopExecuteAsync(IFlowEnvironment env, - SingleFlipflopNode singleFlipFlopNode, - CancellationTokenSource cts) - { - if(_flipFlopCts is null) - { - SereinEnv.WriteLine(InfoType.INFO, "流程尚未启动,flowStarter尚未创建,无法启动该节点"); - return; - } - - while (!_flipFlopCts.IsCancellationRequested && !cts.IsCancellationRequested) - { - try - { - var context = new Library.DynamicContext(env); // 启动全局触发器时新建上下文 - var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); // 获取触发器等待Task - context.AddOrUpdate(singleFlipFlopNode.Guid, newFlowData); - await NodeModelBase.RefreshFlowDataAndExpInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据 - if (context.NextOrientation == ConnectionInvokeType.None) - { - continue; - } - _ = Task.Run(async () => { - var nextNodes = singleFlipFlopNode.SuccessorNodes[context.NextOrientation]; - for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--) - { - // 筛选出启用的节点 - if (!nextNodes[i].DebugSetting.IsEnable) - { - continue ; - } - - context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); - if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前 - { - await nextNodes[i].DebugSetting.GetInterruptTask.Invoke(); - await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); - } - await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点 - } - - nextNodes = singleFlipFlopNode.SuccessorNodes[ConnectionInvokeType.Upstream]; - for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--) - { - // 筛选出启用的节点 - if (!nextNodes[i].DebugSetting.IsEnable) - { - continue; - } - - context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); - if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前 - { - await nextNodes[i].DebugSetting.GetInterruptTask.Invoke(); - await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); - } - await nextNodes[i].StartFlowAsync(context); // 启动执行触发器后继分支的节点 - } - - context.Exit(); - }); - - } - catch (FlipflopException ex) - { - SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message); - if (ex.Type == FlipflopException.CancelClass.CancelFlow) - { - break; - } - } - catch (Exception ex) - { - SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.Guid}]异常。"+ ex.Message); - await Task.Delay(1000); - } - } - - } - - /// - /// 结束流程 - /// - public void Exit() - { - ExitAction?.Invoke(); - - } - - } -} - - - - diff --git a/NodeFlow/FlowTaskLibrary.cs b/NodeFlow/FlowTaskLibrary.cs new file mode 100644 index 0000000..f6eb5c1 --- /dev/null +++ b/NodeFlow/FlowTaskLibrary.cs @@ -0,0 +1,52 @@ +using Microsoft.Extensions.ObjectPool; +using Serein.Library; +using Serein.Library.Api; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Serein.NodeFlow +{ + + public class FlowTaskLibrary() + { + /// + /// 流程运行环境 + /// + public IFlowEnvironment Environment { get; set; }// = environment; + + /// + /// 表示运行环境状态 + /// + public CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource(); + + /// + /// 上下文线程池 + /// + public Serein.Library.Utils.ObjectPool FlowContextPool { get; set; } + + /// + /// 当前任务加载的所有节点 + /// + public List Nodes { get; set; }// = nodes; + /// + /// 需要注册的类型 + /// + public Dictionary> AutoRegisterTypes { get; set; } //= autoRegisterTypes; + /// + /// 初始化时需要的方法 + /// + public List InitMds { get; set; }// = initMds; + /// + /// 加载时需要的方法 + /// + public List LoadMds { get; set; }// = loadMds; + /// + /// 退出时需要调用的方法 + /// + public List ExitMds { get; set; } //= exitMds; + } + +} diff --git a/NodeFlow/FlowWorkManagement.cs b/NodeFlow/FlowWorkManagement.cs new file mode 100644 index 0000000..2953505 --- /dev/null +++ b/NodeFlow/FlowWorkManagement.cs @@ -0,0 +1,386 @@ +using Microsoft.CodeAnalysis; +using Serein.Library; +using Serein.Library.Api; +using Serein.Library.Utils; +using Serein.NodeFlow.Model; +using Serein.NodeFlow.Tool; +using System; +using System.Collections.Concurrent; +using System.Xml.Linq; + +namespace Serein.NodeFlow +{ + /// + /// 流程任务管理 + /// + public class FlowWorkManagement + { + /// + /// 触发器对应的Cts + /// + private ConcurrentDictionary dictGlobalFlipflop = []; + + + + + /// + /// 结束运行时需要执行的方法 + /// + private Func? ExitAction { get; set; } + /// + /// 初始化选项 + /// + public FlowTaskLibrary WorkLibrary { get; } + + /// + /// 流程任务管理 + /// + /// + public FlowWorkManagement(FlowTaskLibrary library) + { + WorkLibrary = library; + + } + + /// + /// 初始化啊 + /// + /// + public async Task RunAsync(CancellationToken token) + { + NodeModelBase? startNode = WorkLibrary.Nodes.FirstOrDefault(node => node.IsStart); + if (startNode is null) + { + return false; + } + + if (!RegisterAllType()) + { + return false; + }; + var initState = await TryInit(); + if (!initState) + { + return false; + }; + var loadState = await TryLoadAsync(); + if (!loadState) + { + return false; + }; + var task = CallFlipflopNode(); + await CallStartNode(startNode); + await task; + await CallExit(); + return true; + } + + #region 初始化 + private bool RegisterAllType() + { + var env = WorkLibrary.Environment; + var nodeMds = WorkLibrary.Nodes.Select(item => item.MethodDetails).ToList(); // 获取环境中所有节点的方法信息 + var allMds = new List(); + allMds.AddRange(nodeMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkLibrary.InitMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkLibrary.LoadMds.Where(md => md?.ActingInstanceType is not null)); + allMds.AddRange(WorkLibrary.ExitMds.Where(md => md?.ActingInstanceType is not null)); + var isSuccessful = true; + foreach (var md in allMds) + { + if (md.ActingInstanceType != null) + { + env.IOC.Register(md.ActingInstanceType); + } + else + { + SereinEnv.WriteLine(InfoType.ERROR, "{md.MethodName} - 没有类型声明"); + isSuccessful = false ; + } + } + env.IOC.Build(); // 绑定初始化时注册的类型 + foreach (var md in allMds) + { + var instance = env.IOC.Get(md.ActingInstanceType); + if (instance is null) + { + SereinEnv.WriteLine(InfoType.ERROR, $"{md.MethodName} - 无法获取类型[{md.ActingInstanceType}]的实例"); + isSuccessful = false; + } + } + + return isSuccessful; + } + + private async Task TryInit() + { + var env = WorkLibrary.Environment; + var initMds = WorkLibrary.InitMds; + var pool = WorkLibrary.FlowContextPool; + var ioc = WorkLibrary.Environment.IOC; + foreach (var md in initMds) // 初始化 + { + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 + { + throw new Exception("不存在对应委托"); + } + var context = pool.Allocate(); + var instance = ioc.Get(md.ActingInstanceType); + await dd.InvokeAsync(instance, [context]); + context.Reset(); + pool.Free(context); + } + env.IOC.Build(); // 绑定初始化时注册的类型 + var isSuccessful = true; + return isSuccessful; + } + private async Task TryLoadAsync() + { + var env = WorkLibrary.Environment; + var loadMds = WorkLibrary.LoadMds; + var pool = WorkLibrary.FlowContextPool; + var ioc = WorkLibrary.Environment.IOC; + foreach (var md in loadMds) // 加载时 + { + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 + { + throw new Exception("不存在对应委托"); + } + var context = pool.Allocate(); + var instance = ioc.Get(md.ActingInstanceType); + await dd.InvokeAsync(instance, [context]); + context.Reset(); + pool.Free(context); + } + env.IOC.Build(); // 绑定初始化时注册的类型 + var isSuccessful = true; + return isSuccessful; + + } + private async Task CallExit() + { + var env = WorkLibrary.Environment; + var mds = WorkLibrary.ExitMds; + var pool = WorkLibrary.FlowContextPool; + var ioc = WorkLibrary.Environment.IOC; + + ioc.Run(fit => fit.CancelAllTrigger());// 取消所有中断 + foreach (var md in mds) // 结束时 + { + if (!env.TryGetDelegateDetails(md.AssemblyName, md.MethodName, out var dd)) // 流程运行初始化 + { + throw new Exception("不存在对应委托"); + } + var context = pool.Allocate(); + var instance = ioc.Get(md.ActingInstanceType); + await dd.InvokeAsync(instance, [context]); + context.Reset(); + pool.Free(context); + } + + TerminateAllGlobalFlipflop(); // 确保所有触发器不再运行 + SereinEnv.ClearFlowGlobalData(); // 清空全局数据缓存 + NativeDllHelper.FreeLibrarys(); // 卸载所有已加载的 Native Dll + + var isSuccessful = true; + return isSuccessful; + } + + private Task CallFlipflopNode() + { + var env = WorkLibrary.Environment; + var flipflopNodes = WorkLibrary.Nodes.Where(item => item is SingleFlipflopNode node + && !node.IsStart + && node.DebugSetting.IsEnable + && node.NotExitPreviousNode()) + .Select(item => (SingleFlipflopNode)item); + //.ToList();// 获取需要再运行开始之前启动的触发器节点 + + if (flipflopNodes.Count() > 0) + { + var tasks = flipflopNodes.Select(async node => + { + await RunGlobalFlipflopAsync(env, node); // 启动流程时启动全局触发器 + }); + Task.WhenAll(tasks); + } + return Task.CompletedTask; + } + private async Task CallStartNode(NodeModelBase startNode) + { + var pool = WorkLibrary.FlowContextPool; + var token = WorkLibrary.CancellationTokenSource.Token; + var context = pool.Allocate(); + await startNode.StartFlowAsync(context, token); + context.Exit(); + pool.Free(context); + return; + } + + #endregion + + /// + /// 从选定的节点开始运行 + /// + /// + /// + /// + public async Task StartFlowInSelectNodeAsync(IFlowEnvironment env, NodeModelBase startNode) + { + var pool = WorkLibrary.FlowContextPool; + var context = pool.Allocate(); + var token = WorkLibrary.CancellationTokenSource.Token; + await startNode.StartFlowAsync(context, token); // 开始运行时从选定节点开始运行 + context.Reset(); + pool.Free(context); + } + + + + + /// + /// 尝试添加全局触发器 + /// + /// + /// + public async Task RunGlobalFlipflopAsync(IFlowEnvironment env, SingleFlipflopNode singleFlipFlopNode) + { + if (dictGlobalFlipflop.TryAdd(singleFlipFlopNode, new CancellationTokenSource())) + { + var cts = dictGlobalFlipflop[singleFlipFlopNode]; + await FlipflopExecuteAsync(singleFlipFlopNode, cts.Token); + } + } + + /// + /// 尝试移除全局触发器 + /// + /// + public void TerminateGlobalFlipflopRuning(SingleFlipflopNode singleFlipFlopNode) + { + if (dictGlobalFlipflop.TryRemove(singleFlipFlopNode, out var cts)) + { + if (!cts.IsCancellationRequested) + { + cts.Cancel(); + } + cts.Dispose(); + } + } + + /// + /// 终结所有全局触发器 + /// + private void TerminateAllGlobalFlipflop() + { + foreach ((var node, var cts) in dictGlobalFlipflop) + { + if (!cts.IsCancellationRequested) + { + cts.Cancel(); + } + cts.Dispose(); + } + dictGlobalFlipflop.Clear(); + } + + /// + /// 启动全局触发器 + /// + /// 需要全局监听信号的触发器 + /// 单个触发器持有的 + /// + private async Task FlipflopExecuteAsync(SingleFlipflopNode singleFlipFlopNode, + CancellationToken singleToken) + { + + var pool = WorkLibrary.FlowContextPool; + while (!singleToken.IsCancellationRequested && !singleToken.IsCancellationRequested) + { + try + { + var context = pool.Allocate(); // 启动全局触发器时新建上下文 + var newFlowData = await singleFlipFlopNode.ExecutingAsync(context, singleToken); // 获取触发器等待Task + context.AddOrUpdate(singleFlipFlopNode.Guid, newFlowData); + if (context.NextOrientation == ConnectionInvokeType.None) + { + continue; + } + _ = Task.Run(() => CallSubsequentNode(singleFlipFlopNode, singleToken, pool, context)); // 重新启动触发器 + + } + catch (FlipflopException ex) + { + SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.MethodDetails.MethodName}]因非预期异常终止。"+ex.Message); + if (ex.Type == FlipflopException.CancelClass.CancelFlow) + { + break; + } + } + catch (Exception ex) + { + SereinEnv.WriteLine(InfoType.ERROR, $"触发器[{singleFlipFlopNode.Guid}]异常。"+ ex.Message); + await Task.Delay(100); + } + } + + } + + private static async Task? CallSubsequentNode(SingleFlipflopNode singleFlipFlopNode, CancellationToken singleToken, ObjectPool pool, IDynamicContext context) + { + var flowState = context.NextOrientation; // 记录一下流程状态 + var nextNodes = singleFlipFlopNode.SuccessorNodes[ConnectionInvokeType.Upstream]; // 优先调用上游分支 + for (int i = nextNodes.Count - 1; i >= 0 && !singleToken.IsCancellationRequested; i--) + { + // 筛选出启用的节点 + if (!nextNodes[i].DebugSetting.IsEnable) + { + continue; + } + context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); // 设置调用关系 + + if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前检查终端 + { + await nextNodes[i].DebugSetting.GetInterruptTask.Invoke(); + await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); + } + await nextNodes[i].StartFlowAsync(context, singleToken); // 启动执行触发器后继分支的节点 + } + + nextNodes = singleFlipFlopNode.SuccessorNodes[flowState]; // 调用对应分支 + for (int i = nextNodes.Count - 1; i >= 0 && !singleToken.IsCancellationRequested; i--) + { + // 筛选出启用的节点 + if (!nextNodes[i].DebugSetting.IsEnable) + { + continue; + } + + context.SetPreviousNode(nextNodes[i], singleFlipFlopNode); + if (nextNodes[i].DebugSetting.IsInterrupt) // 执行触发前 + { + await nextNodes[i].DebugSetting.GetInterruptTask.Invoke(); + await Console.Out.WriteLineAsync($"[{nextNodes[i].MethodDetails.MethodName}]中断已取消,开始执行后继分支"); + } + await nextNodes[i].StartFlowAsync(context, singleToken); // 启动执行触发器后继分支的节点 + } + + context.Reset(); + pool.Free(context); + } + + /// + /// 结束流程 + /// + public void Exit() + { + ExitAction?.Invoke(); + + } + + } +} + + + + diff --git a/NodeFlow/Model/CompositeConditionNode.cs b/NodeFlow/Model/CompositeConditionNode.cs index 23cfe97..23212a2 100644 --- a/NodeFlow/Model/CompositeConditionNode.cs +++ b/NodeFlow/Model/CompositeConditionNode.cs @@ -36,14 +36,18 @@ namespace Serein.NodeFlow.Model /// /// /// - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { try { // 条件区域中遍历每个条件节点 foreach (SingleConditionNode? node in ConditionNodes) { - var state = await node.ExecutingAsync(context); + if (token.IsCancellationRequested) + { + return null; + } + var state = await node.ExecutingAsync(context, token); if (context.NextOrientation != ConnectionInvokeType.IsSucceed) { // 如果条件不通过,立刻推出循环 diff --git a/NodeFlow/Model/SingleConditionNode.cs b/NodeFlow/Model/SingleConditionNode.cs index 48ee5ab..64f4dbd 100644 --- a/NodeFlow/Model/SingleConditionNode.cs +++ b/NodeFlow/Model/SingleConditionNode.cs @@ -110,8 +110,9 @@ namespace Serein.NodeFlow.Model /// /// /// - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { + if (token.IsCancellationRequested) return null; // 接收上一节点参数or自定义参数内容 object? parameter; object? result = null; diff --git a/NodeFlow/Model/SingleExpOpNode.cs b/NodeFlow/Model/SingleExpOpNode.cs index c7844e4..8935cbf 100644 --- a/NodeFlow/Model/SingleExpOpNode.cs +++ b/NodeFlow/Model/SingleExpOpNode.cs @@ -1,4 +1,5 @@ -using Serein.Library; +using Newtonsoft.Json.Linq; +using Serein.Library; using Serein.Library.Api; using Serein.Library.Utils; using Serein.Library.Utils.SereinExpression; @@ -91,8 +92,10 @@ namespace Serein.NodeFlow.Model } - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { + if(token.IsCancellationRequested) return null; + object? parameter = null;// context.TransmissionData(this); // 表达式节点使用上一节点数据 var pd = MethodDetails.ParameterDetailss[0]; diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index a8cc1ff..e68d5aa 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -1,6 +1,7 @@ using Serein.Library.Api; using Serein.Library; using Serein.Library.Utils; +using System; namespace Serein.NodeFlow.Model { @@ -21,7 +22,7 @@ namespace Serein.NodeFlow.Model /// /// /// - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { #region 执行前中断 if (DebugSetting.IsInterrupt) // 执行触发前 @@ -37,13 +38,18 @@ namespace Serein.NodeFlow.Model { throw new Exception("不存在对应委托"); } - object instance = md.ActingInstance; - var args = await GetParametersAsync(context); + var instance = context.Env.IOC.Get(md.ActingInstanceType); + await dd.InvokeAsync(instance, [context]); + var args = await GetParametersAsync(context, token); // 因为这里会返回不确定的泛型 IFlipflopContext // 而我们只需要获取到 State 和 Value(返回的数据) // 所以使用 dynamic 类型接收 - dynamic dynamicFlipflopContext = await dd.InvokeAsync(md.ActingInstance, args); + if (token.IsCancellationRequested) + { + return null; + } + dynamic dynamicFlipflopContext = await dd.InvokeAsync(instance, args); FlipflopStateType flipflopStateType = dynamicFlipflopContext.State; context.NextOrientation = flipflopStateType.ToContentType(); diff --git a/NodeFlow/Model/SingleGlobalDataNode.cs b/NodeFlow/Model/SingleGlobalDataNode.cs index 56db45f..2e3f063 100644 --- a/NodeFlow/Model/SingleGlobalDataNode.cs +++ b/NodeFlow/Model/SingleGlobalDataNode.cs @@ -115,8 +115,9 @@ namespace Serein.NodeFlow.Model /// /// /// - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { + if (token.IsCancellationRequested) return null; if (string.IsNullOrEmpty(KeyName)) { context.NextOrientation = ConnectionInvokeType.IsError; diff --git a/NodeFlow/Model/SingleScriptNode.cs b/NodeFlow/Model/SingleScriptNode.cs index fda4f1f..a0f6129 100644 --- a/NodeFlow/Model/SingleScriptNode.cs +++ b/NodeFlow/Model/SingleScriptNode.cs @@ -165,13 +165,15 @@ namespace Serein.NodeFlow.Model /// /// /// - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { - var @params = await GetParametersAsync(context); - + if(token.IsCancellationRequested) return null; + var @params = await GetParametersAsync(context, token); + if(token.IsCancellationRequested) return null; + //context.AddOrUpdate($"{context.Guid}_{this.Guid}_Params", @params[0]); // 后面再改 - ReloadScript();// 每次都重新解析 + ReloadScript();// 每次都重新解析 IScriptInvokeContext scriptContext = new ScriptInvokeContext(context); @@ -193,6 +195,9 @@ namespace Serein.NodeFlow.Model var envEvent = (IFlowEnvironmentEvent)context.Env; envEvent.OnFlowRunComplete += onFlowStop; // 防止运行后台流程 + + if (token.IsCancellationRequested) return null; + var result = await ScriptInterpreter.InterpretAsync(scriptContext, mainNode); // 从入口节点执行 envEvent.OnFlowRunComplete -= onFlowStop; //SereinEnv.WriteLine(InfoType.INFO, "FlowContext Guid : " + context.Guid); diff --git a/NodeFlow/Model/SingleUINode.cs b/NodeFlow/Model/SingleUINode.cs index a438f42..bbf7955 100644 --- a/NodeFlow/Model/SingleUINode.cs +++ b/NodeFlow/Model/SingleUINode.cs @@ -15,12 +15,13 @@ namespace Serein.NodeFlow.Model { } - public override async Task ExecutingAsync(IDynamicContext context) + public override async Task ExecutingAsync(IDynamicContext context, CancellationToken token) { + if (token.IsCancellationRequested) return null; if(Adapter is null) { - var result = await base.ExecutingAsync(context); + var result = await base.ExecutingAsync(context, token); if (result is IEmbeddedContent adapter) { this.Adapter = adapter; @@ -39,7 +40,7 @@ namespace Serein.NodeFlow.Model iflowContorl.OnExecuting(data); } - return Task.FromResult(null); + return null; } } } diff --git a/Workbench/App.xaml b/Workbench/App.xaml index c5398c3..44bc631 100644 --- a/Workbench/App.xaml +++ b/Workbench/App.xaml @@ -3,8 +3,10 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:Serein.Workbench" xmlns:view="clr-namespace:Serein.Workbench.Views" - StartupUri="Views/FlowWorkbenchView.xaml" + StartupUri="MainWindow.xaml" Startup="Application_Startup"> + + diff --git a/Workbench/App.xaml.cs b/Workbench/App.xaml.cs index 99fb86a..2b6f435 100644 --- a/Workbench/App.xaml.cs +++ b/Workbench/App.xaml.cs @@ -90,17 +90,19 @@ namespace Serein.Workbench public App() { + _ = Task.Run(async () => + { + await Task.Delay(500); + await this.LoadLocalProjectAsync(); + }); + return; var collection = new ServiceCollection(); collection.AddWorkbenchServices(); collection.AddFlowServices(); collection.AddViewModelServices(); var services = collection.BuildServiceProvider(); // 绑定并返回获取实例的服务接口 App.ServiceProvider = services; - _ = Task.Run(async () => - { - await Task.Delay(500); - await this.LoadLocalProjectAsync(); - }); + } @@ -115,14 +117,14 @@ namespace Serein.Workbench filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\net8.0\PLCproject.dnf"; filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\Release\banyunqi\project.dnf"; filePath = @"F:\临时\project\project.dnf"; - filePath = @"F:\临时\flow\qrcode\project.dnf"; + filePath = @"F:\TempFile\flow\qrcode\project.dnf"; //filePath = @"C:\Users\Az\source\repos\CLBanyunqiState\CLBanyunqiState\bin\debug\net8.0\test.dnf"; string content = System.IO.File.ReadAllText(filePath); // 读取整个文件内容 App.FlowProjectData = JsonConvert.DeserializeObject(content); App.FileDataPath = System.IO.Path.GetDirectoryName(filePath)!; // filePath;// var dir = Path.GetDirectoryName(filePath); - App.GetService().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData },App.FileDataPath); + //App.GetService().LoadProject(new FlowEnvInfo { Project = App.FlowProjectData },App.FileDataPath); } #endif } diff --git a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs index 9783c0c..bac4fa8 100644 --- a/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/ScriptNodeControlViewModel.cs @@ -29,7 +29,9 @@ namespace Serein.Workbench.Node.ViewModel { try { - var result = await NodeModel.ExecutingAsync(new Library.DynamicContext(nodeModel.Env)); + var cts = new CancellationTokenSource(); + var result = await NodeModel.ExecutingAsync(new Library.DynamicContext(nodeModel.Env), cts.Token); + cts.Cancel(); SereinEnv.WriteLine(InfoType.INFO, result?.ToString()); } catch (Exception ex) diff --git a/Workbench/Node/ViewModel/UINodeControlViewModel.cs b/Workbench/Node/ViewModel/UINodeControlViewModel.cs index b4606d0..40e74cf 100644 --- a/Workbench/Node/ViewModel/UINodeControlViewModel.cs +++ b/Workbench/Node/ViewModel/UINodeControlViewModel.cs @@ -25,7 +25,9 @@ namespace Serein.Workbench.Node.ViewModel Task.Factory.StartNew(async () => { var context = new DynamicContext(NodeModel.Env); - await NodeModel.ExecutingAsync(context); + var cts = new CancellationTokenSource(); + await NodeModel.ExecutingAsync(context, cts.Token); + cts?.Dispose(); if (context.NextOrientation == ConnectionInvokeType.IsSucceed && NodeModel.Adapter.GetUserControl() is UserControl userControl) {