From b63a5f4c62939b4be0c901be922eba0bab080698 Mon Sep 17 00:00:00 2001 From: fengjiayi <12821976+ning_xi@user.noreply.gitee.com> Date: Sun, 22 Sep 2024 17:37:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=E4=B8=AD=E6=96=AD?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Library.Core/NodeFlow/DynamicContext.cs | 24 +- Library.Framework/NodeFlow/DynamicContext.cs | 12 +- .../Serein.Library.Framework.csproj | 3 + Library.Framework/packages.config | 1 + Library/Api/IFlowEnvironment.cs | 38 +- Library/Entity/NodeDebugSetting.cs | 19 +- Library/Utils/ChannelFlowInterrupt.cs | 205 ++- NodeFlow/Base/NodeModelBaseData.cs | 36 +- NodeFlow/Base/NodeModelBaseFunc.cs | 143 +- NodeFlow/FlowEnvironment.cs | 134 +- NodeFlow/FlowStarter.cs | 8 +- NodeFlow/Model/SingleFlipflopNode.cs | 12 +- WorkBench/MainWindow.xaml.cs | 86 +- WorkBench/Node/NodeControlViewModelBase.cs | 6 +- WorkBench/Node/View/ActionNodeControl.xaml | 1 - WorkBench/Themes/ObjectViewerControl.xaml | 6 +- WorkBench/Themes/ObjectViewerControl.xaml.cs | 23 +- .../System.ValueTuple.4.5.0/.signature.p7s | Bin 0 -> 22354 bytes packages/System.ValueTuple.4.5.0/LICENSE.TXT | 23 + .../System.ValueTuple.4.5.0.nupkg | Bin 0 -> 204904 bytes .../THIRD-PARTY-NOTICES.TXT | 309 ++++ .../lib/MonoAndroid10/_._ | 0 .../lib/MonoTouch10/_._ | 0 .../lib/net461/System.ValueTuple.xml | 1299 +++++++++++++++++ .../lib/net47/System.ValueTuple.xml | 8 + .../lib/netcoreapp2.0/_._ | 0 .../lib/netstandard1.0/System.ValueTuple.xml | 1299 +++++++++++++++++ .../lib/netstandard2.0/_._ | 0 .../System.ValueTuple.xml | 1299 +++++++++++++++++ .../lib/uap10.0.16299/_._ | 0 .../lib/xamarinios10/_._ | 0 .../lib/xamarinmac20/_._ | 0 .../lib/xamarintvos10/_._ | 0 .../lib/xamarinwatchos10/_._ | 0 .../ref/MonoAndroid10/_._ | 0 .../ref/MonoTouch10/_._ | 0 .../ref/netcoreapp2.0/_._ | 0 .../ref/netstandard2.0/_._ | 0 .../ref/uap10.0.16299/_._ | 0 .../ref/xamarinios10/_._ | 0 .../ref/xamarinmac20/_._ | 0 .../ref/xamarintvos10/_._ | 0 .../ref/xamarinwatchos10/_._ | 0 .../useSharedDesignerContext.txt | 0 packages/System.ValueTuple.4.5.0/version.txt | 1 + 45 files changed, 4821 insertions(+), 174 deletions(-) create mode 100644 packages/System.ValueTuple.4.5.0/.signature.p7s create mode 100644 packages/System.ValueTuple.4.5.0/LICENSE.TXT create mode 100644 packages/System.ValueTuple.4.5.0/System.ValueTuple.4.5.0.nupkg create mode 100644 packages/System.ValueTuple.4.5.0/THIRD-PARTY-NOTICES.TXT create mode 100644 packages/System.ValueTuple.4.5.0/lib/MonoAndroid10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/MonoTouch10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/net461/System.ValueTuple.xml create mode 100644 packages/System.ValueTuple.4.5.0/lib/net47/System.ValueTuple.xml create mode 100644 packages/System.ValueTuple.4.5.0/lib/netcoreapp2.0/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/netstandard1.0/System.ValueTuple.xml create mode 100644 packages/System.ValueTuple.4.5.0/lib/netstandard2.0/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/portable-net40+sl4+win8+wp8/System.ValueTuple.xml create mode 100644 packages/System.ValueTuple.4.5.0/lib/uap10.0.16299/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/xamarinios10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/xamarinmac20/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/xamarintvos10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/lib/xamarinwatchos10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/MonoAndroid10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/MonoTouch10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/netcoreapp2.0/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/netstandard2.0/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/uap10.0.16299/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/xamarinios10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/xamarinmac20/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/xamarintvos10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/ref/xamarinwatchos10/_._ create mode 100644 packages/System.ValueTuple.4.5.0/useSharedDesignerContext.txt create mode 100644 packages/System.ValueTuple.4.5.0/version.txt diff --git a/Library.Core/NodeFlow/DynamicContext.cs b/Library.Core/NodeFlow/DynamicContext.cs index be81db1..e6aeffc 100644 --- a/Library.Core/NodeFlow/DynamicContext.cs +++ b/Library.Core/NodeFlow/DynamicContext.cs @@ -22,15 +22,29 @@ namespace Serein.Library.Core.NodeFlow public Task CreateTimingTask(Action action, int time = 100, int count = -1) { - NodeRunCts ??= SereinIoc.GetOrRegisterInstantiate(); - return Task.Factory.StartNew(async () => + if (NodeRunCts == null) { - for (int i = 0; i < count; i++) + NodeRunCts = SereinIoc.GetOrRegisterInstantiate(); + } + // 使用局部变量,避免捕获外部的 `action` + Action localAction = action; + + return Task.Run(async () => + { + for (int i = 0; i < count && !NodeRunCts.IsCancellationRequested; i++) { - NodeRunCts.Token.ThrowIfCancellationRequested(); await Task.Delay(time); - action.Invoke(); + if (NodeRunCts.IsCancellationRequested) { break; } + if (FlowEnvironment.IsGlobalInterrupt) + { + await FlowEnvironment.GetOrCreateGlobalInterruptAsync(); + } + // 确保对局部变量的引用 + localAction?.Invoke(); } + + // 清理引用,避免闭包导致的内存泄漏 + localAction = null; }); } } diff --git a/Library.Framework/NodeFlow/DynamicContext.cs b/Library.Framework/NodeFlow/DynamicContext.cs index f245204..f41d2ba 100644 --- a/Library.Framework/NodeFlow/DynamicContext.cs +++ b/Library.Framework/NodeFlow/DynamicContext.cs @@ -1,6 +1,7 @@ using Serein.Library.Api; using Serein.Library.Utils; using System; +using System.Security.Claims; using System.Threading.Tasks; namespace Serein.Library.Framework.NodeFlow @@ -17,9 +18,11 @@ namespace Serein.Library.Framework.NodeFlow SereinIoc = sereinIoc; FlowEnvironment = flowEnvironment; } + public NodeRunCts NodeRunCts { get; set; } public ISereinIOC SereinIoc { get; } public IFlowEnvironment FlowEnvironment { get; } + public Task CreateTimingTask(Action action, int time = 100, int count = -1) { if(NodeRunCts == null) @@ -31,11 +34,14 @@ namespace Serein.Library.Framework.NodeFlow return Task.Run(async () => { - for (int i = 0; i < count; i++) + for (int i = 0; i < count && !NodeRunCts.IsCancellationRequested; i++) { - NodeRunCts.Token.ThrowIfCancellationRequested(); await Task.Delay(time); - + if (NodeRunCts.IsCancellationRequested) { break; } + if (FlowEnvironment.IsGlobalInterrupt) + { + await FlowEnvironment.GetOrCreateGlobalInterruptAsync(); + } // 确保对局部变量的引用 localAction?.Invoke(); } diff --git a/Library.Framework/Serein.Library.Framework.csproj b/Library.Framework/Serein.Library.Framework.csproj index 9c4327c..5332cb5 100644 --- a/Library.Framework/Serein.Library.Framework.csproj +++ b/Library.Framework/Serein.Library.Framework.csproj @@ -37,6 +37,9 @@ + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + diff --git a/Library.Framework/packages.config b/Library.Framework/packages.config index d04b6cf..284ce17 100644 --- a/Library.Framework/packages.config +++ b/Library.Framework/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/Library/Api/IFlowEnvironment.cs b/Library/Api/IFlowEnvironment.cs index c98293f..70fe45b 100644 --- a/Library/Api/IFlowEnvironment.cs +++ b/Library/Api/IFlowEnvironment.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Reflection; using System.Threading.Tasks; +using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.Library.Api { @@ -285,8 +286,23 @@ namespace Serein.Library.Api public interface IFlowEnvironment { - ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } + /// + /// 环境名称 + /// + string EnvName {get;} + /// + /// 是否全局中断 + /// + bool IsGlobalInterrupt { get; } + /// + /// 设置中断时的中断级别 + /// + //InterruptClass EnvInterruptClass { get; set; } + /// + /// 调试管理 + /// + //ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } /// /// 加载Dll @@ -417,7 +433,15 @@ namespace Serein.Library.Api /// 被中断的节点Guid /// 新的中断级别 /// - bool NodeInterruptChange(string nodeGuid,InterruptClass interruptClass); + bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass); + + /// + /// 添加中断表达式 + /// + /// + /// + /// + bool AddInterruptExpression(string nodeGuid,string expression); /// /// /// @@ -431,7 +455,15 @@ namespace Serein.Library.Api /// 节点数据更新通知 /// /// - void FlowDataUpdateNotification(string nodeGuid, object flowData); + void FlowDataNotification(string nodeGuid, object flowData); + + /// + /// 全局中断 + /// + /// + /// + /// + Task GetOrCreateGlobalInterruptAsync(); } } diff --git a/Library/Entity/NodeDebugSetting.cs b/Library/Entity/NodeDebugSetting.cs index 9008fb7..de5bed9 100644 --- a/Library/Entity/NodeDebugSetting.cs +++ b/Library/Entity/NodeDebugSetting.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading.Tasks; +using static Serein.Library.Utils.ChannelFlowInterrupt; namespace Serein.Library.Entity { @@ -21,8 +23,21 @@ namespace Serein.Library.Entity /// public InterruptClass InterruptClass { get; set; } = InterruptClass.None; + /// + /// 中断表达式 + /// + public List InterruptExpressions { get; } = new List(); - public List InterruptExpression { get; } = new List(); + + /// + /// 取消中断的回调函数 + /// + public Action CancelInterruptCallback { get; set; } + + /// + /// 中断Task + /// + public Func> GetInterruptTask { get; set; } } /// @@ -41,7 +56,7 @@ namespace Serein.Library.Entity /// /// 分组中断,中断进入指定节点分组的分支。(暂未实现相关) /// - Group, + // Group, /// /// 全局中断,中断全局所有节点的运行。(暂未实现相关) /// diff --git a/Library/Utils/ChannelFlowInterrupt.cs b/Library/Utils/ChannelFlowInterrupt.cs index 22ced6c..81c0522 100644 --- a/Library/Utils/ChannelFlowInterrupt.cs +++ b/Library/Utils/ChannelFlowInterrupt.cs @@ -6,8 +6,14 @@ using System.Threading.Tasks; namespace Serein.Library.Utils { + /// + /// 流程中断管理 + /// public class ChannelFlowInterrupt { + + + /// /// 中断取消类型 /// @@ -26,7 +32,7 @@ namespace Serein.Library.Utils /// 信号标识符 /// 超时时间 /// 等待任务 - public async Task CreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) + public async Task GetCreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) { var channel = GetOrCreateChannel(signal); var cts = new CancellationTokenSource(); @@ -37,11 +43,11 @@ namespace Serein.Library.Utils try { await Task.Delay(outTime, cts.Token); - if(!cts.Token.IsCancellationRequested) + if (!cts.Token.IsCancellationRequested) { await channel.Writer.WriteAsync(CancelType.Overtime); } - + } catch (OperationCanceledException) { @@ -58,6 +64,21 @@ namespace Serein.Library.Utils return result; } + + /// + /// 创建信号,直到手动触发(异步方法) + /// + /// 信号标识符 + /// 超时时间 + /// 等待任务 + public async Task GetOrCreateChannelAsync(string signal) + { + var channel = GetOrCreateChannel(signal); + // 等待信号传入(超时或手动触发) + var result = await channel.Reader.ReadAsync(); + return result; + } + /// /// 创建信号并指定超时时间,到期后自动触发(同步阻塞方法) /// @@ -95,13 +116,30 @@ namespace Serein.Library.Utils /// 是否成功触发 public bool TriggerSignal(string signal) { + //if (_channels.TryGetValue(signal, out var channel)) + //{ + // // 手动触发信号 + // channel.Writer.TryWrite(CancelType.Manual); + // return true; + //} + //return false; + + if (_channels.TryGetValue(signal, out var channel)) { // 手动触发信号 channel.Writer.TryWrite(CancelType.Manual); + + // 完成写入,标记该信号通道不再接受新写入 + channel.Writer.Complete(); + + // 触发后移除信号 + _channels.TryRemove(signal, out _); + return true; } return false; + } /// @@ -111,7 +149,14 @@ namespace Serein.Library.Utils { foreach (var channel in _channels.Values) { - channel.Writer.Complete(); + try + { + channel.Writer.Complete(); + } + finally + { + + } } _channels.Clear(); } @@ -128,3 +173,155 @@ namespace Serein.Library.Utils } } + + +//using System; +//using System.Collections.Concurrent; +//using System.Threading; +//using System.Threading.Channels; +//using System.Threading.Tasks; + +//namespace Serein.Library.Utils +//{ +// /// +// /// 流程中断管理类,提供了基于 Channel 的异步中断机制 +// /// +// public class ChannelFlowInterrupt +// { +// /// +// /// 中断取消类型 +// /// +// public enum CancelType +// { +// Manual, // 手动触发 +// Overtime, // 超时触发 +// Discard // 丢弃触发 +// } + +// // 使用并发字典管理每个信号对应的 Channel 和状态 +// private readonly ConcurrentDictionary Channel, bool IsCancelled, bool IsDiscardMode)> _channels +// = new ConcurrentDictionary, bool, bool)>(); + +// // 锁对象,用于保护并发访问 +// private readonly object _lock = new object(); + +// /// +// /// 创建带有超时功能的信号,超时后自动触发 +// /// +// public async Task GetCreateChannelWithTimeoutAsync(string signal, TimeSpan outTime) +// { +// var (channel, isCancelled, isDiscardMode) = GetOrCreateChannel(signal); + +// // 如果信号已取消或在丢弃模式下,立即返回丢弃类型 +// if (isCancelled || isDiscardMode) return CancelType.Discard; + +// var cts = new CancellationTokenSource(); + +// _ = Task.Run(async () => +// { +// try +// { +// await Task.Delay(outTime, cts.Token); +// if (!cts.Token.IsCancellationRequested && !isCancelled) +// { +// await channel.Writer.WriteAsync(CancelType.Overtime); +// } +// } +// catch (OperationCanceledException) +// { +// // 处理任务取消的情况 +// } +// finally +// { +// cts.Dispose(); +// } +// }, cts.Token); + +// return await channel.Reader.ReadAsync(); +// } + +// /// +// /// 创建或获取现有信号,等待手动触发 +// /// +// public async Task GetOrCreateChannelAsync(string signal) +// { +// var (channel, isCancelled, isDiscardMode) = GetOrCreateChannel(signal); + +// // 如果信号已取消或在丢弃模式下,立即返回丢弃类型 +// if (isCancelled || isDiscardMode) return CancelType.Discard; + +// return await channel.Reader.ReadAsync(); +// } + +// /// +// /// 触发信号并将其移除 +// /// +// public bool TriggerSignal(string signal) +// { +// lock (_lock) +// { +// if (_channels.TryGetValue(signal, out var channelInfo)) +// { +// var (channel, isCancelled, isDiscardMode) = channelInfo; + +// // 如果信号未被取消,则触发并标记为已取消 +// if (!isCancelled) +// { +// channel.Writer.TryWrite(CancelType.Manual); +// _channels[signal] = (channel, true, false); // 标记为已取消 +// _channels.TryRemove(signal, out _); // 从字典中移除信号 +// return true; +// } +// } +// } +// return false; +// } + +// /// +// /// 启用丢弃模式,所有后续获取的信号将直接返回丢弃类型 +// /// +// /// 信号标识符 +// public void EnableDiscardMode(string signal,bool state = true) +// { +// lock (_lock) +// { +// if (_channels.TryGetValue(signal, out var channelInfo)) +// { +// var (channel, isCancelled, _) = channelInfo; +// _channels[signal] = (channel, isCancelled, state); // 标记为丢弃模式 +// } +// } +// } + +// /// +// /// 取消所有任务 +// /// +// public void CancelAllTasks() +// { +// foreach (var (channel, _, _) in _channels.Values) +// { +// try +// { +// channel.Writer.Complete(); +// } +// catch +// { +// // 忽略完成时的异常 +// } +// } +// _channels.Clear(); +// } + +// /// +// /// 获取或创建指定信号的 Channel 通道 +// /// +// private (Channel, bool, bool) GetOrCreateChannel(string signal) +// { +// lock (_lock) +// { +// return _channels.GetOrAdd(signal, _ => (Channel.CreateUnbounded(), false, false)); +// } +// } +// } +//} + diff --git a/NodeFlow/Base/NodeModelBaseData.cs b/NodeFlow/Base/NodeModelBaseData.cs index 06ef4ad..7880547 100644 --- a/NodeFlow/Base/NodeModelBaseData.cs +++ b/NodeFlow/Base/NodeModelBaseData.cs @@ -78,10 +78,42 @@ namespace Serein.NodeFlow.Base /// public Exception RuningException { get; set; } = null; + /// - /// 当前传递数据(执行了节点对应的方法,才会存在值) + /// 控制FlowData在同一时间只会被同一个线程更改。 /// - protected object? FlowData { get; set; } = null; + private readonly ReaderWriterLockSlim _flowDataLock = new ReaderWriterLockSlim(); + private object? _flowData; + /// + /// 当前传递数据(执行了节点对应的方法,才会存在值)。 + /// + protected object? FlowData + { + get + { + _flowDataLock.EnterReadLock(); + try + { + return _flowData; + } + finally + { + _flowDataLock.ExitReadLock(); + } + } + set + { + _flowDataLock.EnterWriteLock(); + try + { + _flowData = value; + } + finally + { + _flowDataLock.ExitWriteLock(); + } + } + } } diff --git a/NodeFlow/Base/NodeModelBaseFunc.cs b/NodeFlow/Base/NodeModelBaseFunc.cs index 06f4035..48c5532 100644 --- a/NodeFlow/Base/NodeModelBaseFunc.cs +++ b/NodeFlow/Base/NodeModelBaseFunc.cs @@ -27,15 +27,6 @@ namespace Serein.NodeFlow.Base #region 调试中断 - public Action? CancelInterruptCallback; - - /// - /// 中断节点 - /// - public void Interrupt() - { - this.DebugSetting.InterruptClass = InterruptClass.Branch; - } /// /// 不再中断 @@ -43,9 +34,9 @@ namespace Serein.NodeFlow.Base public void CancelInterrupt() { this.DebugSetting.InterruptClass = InterruptClass.None; - CancelInterruptCallback?.Invoke(); - CancelInterruptCallback = null; + DebugSetting.CancelInterruptCallback?.Invoke(); } + #endregion #region 导出/导入项目文件节点信息 @@ -105,6 +96,17 @@ namespace Serein.NodeFlow.Base /// public async Task StartExecute(IDynamicContext context) { + if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 + { + var cancelType = await this.DebugSetting.GetInterruptTask(); + //if (cancelType == CancelType.Discard) + //{ + // this.NextOrientation = ConnectionType.None; + // return; + //} + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); + return; + } Stack stack = new Stack(); stack.Push(this); @@ -126,27 +128,30 @@ namespace Serein.NodeFlow.Base var upstreamNodes = currentNode.SuccessorNodes[ConnectionType.Upstream]; for (int i = upstreamNodes.Count - 1; i >= 0; i--) { - if (upstreamNodes[i].DebugSetting.IsEnable) // 排除未启用的上游节点 + // 筛选出启用的节点、未被中断的节点 + if (upstreamNodes[i].DebugSetting.IsEnable && upstreamNodes[i].DebugSetting.InterruptClass == InterruptClass.None) { upstreamNodes[i].PreviousNode = currentNode; - var upNewFlowData = await upstreamNodes[i].ExecutingAsync(context); // 执行流程节点的上游分支 - await FlowRefreshDataOrInterrupt(context, upstreamNodes[i], upNewFlowData); // 执行上游分支后刷新上游节点数据 + await upstreamNodes[i].StartExecute(context); // 执行流程节点的上游分支 } } + // 执行当前节点 var newFlowData = await currentNode.ExecutingAsync(context); - await FlowRefreshDataOrInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据 - #endregion - - - #region 执行完成 if (cts == null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None) { // 不再执行 break; } + await RefreshFlowDataAndExpInterrupt(context, currentNode, newFlowData); // 执行当前节点后刷新数据 + #endregion + + + #region 执行完成 + + // 选择后继分支 var nextNodes = currentNode.SuccessorNodes[currentNode.NextOrientation]; @@ -154,8 +159,8 @@ namespace Serein.NodeFlow.Base // 将下一个节点集合中的所有节点逆序推入栈中 for (int i = nextNodes.Count - 1; i >= 0; i--) { - // 排除未启用的节点 - if (nextNodes[i].DebugSetting.IsEnable) + // 筛选出启用的节点、未被中断的节点 + if (nextNodes[i].DebugSetting.IsEnable && nextNodes[i].DebugSetting.InterruptClass == InterruptClass.None) { nextNodes[i].PreviousNode = currentNode; stack.Push(nextNodes[i]); @@ -165,6 +170,15 @@ namespace Serein.NodeFlow.Base } } + public void ThorwExitExecuting(CancellationTokenSource cts,NodeModelBase currentNode) + { + if (cts == null || cts.IsCancellationRequested || currentNode.NextOrientation == ConnectionType.None) + { + // 不再执行 + throw new Exception("退出方法节点的执行"); + } + } + /// /// 执行节点对应的方法 /// @@ -173,12 +187,16 @@ namespace Serein.NodeFlow.Base public virtual async Task ExecutingAsync(IDynamicContext context) { #region 调试中断 - if (DebugSetting.InterruptClass != InterruptClass.None && TryCreateInterruptTask(context, this, out Task? task)) // 执行节点前检查中断 + + if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 { - string guid = this.Guid.ToString(); - this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid); - var cancelType = await task!; - await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + var cancelType = await this.DebugSetting.GetInterruptTask(); + //if(cancelType == CancelType.Discard) + //{ + // this.NextOrientation = ConnectionType.None; + // return null; + //} + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); } #endregion @@ -213,7 +231,6 @@ namespace Serein.NodeFlow.Base } - #region 节点转换的委托类型 public static object? Execution(Action del, object instance) { @@ -326,64 +343,52 @@ namespace Serein.NodeFlow.Base /// 更新节点数据,并检查监视表达式 /// /// - public static async Task FlowRefreshDataOrInterrupt(IDynamicContext context , NodeModelBase nodeModel, object? newData = null) + public static async Task RefreshFlowDataAndExpInterrupt(IDynamicContext context , NodeModelBase nodeModel, object? newData = null) { string guid = nodeModel.Guid; - if (newData is not null) + // 检查是否存在监视表达式 + if (newData is not null && nodeModel.DebugSetting.InterruptExpressions.Count > 0) { - // 判断是否存在表达式 - bool isInterrupt = false; - // 判断监视表达式 - for (int i = 0; i < nodeModel.DebugSetting.InterruptExpression.Count && !isInterrupt; i++) + // 表达式环境下判断是否需要执行中断 + bool isExpInterrupt = false; + + // 判断执行监视表达式,直到为 true 时退出 + for (int i = 0; i < nodeModel.DebugSetting.InterruptExpressions.Count && !isExpInterrupt; i++) { - string? exp = nodeModel.DebugSetting.InterruptExpression[i]; - isInterrupt = SereinConditionParser.To(newData, exp); + string? exp = nodeModel.DebugSetting.InterruptExpressions[i]; + isExpInterrupt = SereinConditionParser.To(newData, exp); } - if (isInterrupt) // 触发中断 + + if (isExpInterrupt) // 触发中断 { - nodeModel.Interrupt(); - if(TryCreateInterruptTask(context, nodeModel, out Task? task)) + InterruptClass interruptClass = InterruptClass.Branch; // 分支中断 + if (context.FlowEnvironment.SetNodeInterrupt(nodeModel.Guid, interruptClass)) { - - nodeModel.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid); - var cancelType = await task!; - await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + var cancelType = await nodeModel.DebugSetting.GetInterruptTask(); + //if (cancelType == CancelType.Discard) + //{ + // nodeModel.NextOrientation = ConnectionType.None; + // return; + //} + await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); } + } } - nodeModel.FlowData = newData; + //else if (nodeModel.DebugSetting.InterruptClass != InterruptClass.None) + //{ + // var cancelType = await nodeModel.DebugSetting.InterruptTask; + // await Console.Out.WriteLineAsync($"[{nodeModel.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + //} + + nodeModel.FlowData = newData; // 替换数据 // 节点是否监视了数据,如果是,调用环境接口触发其相关事件。 if (nodeModel.DebugSetting.IsMonitorFlowData) { - context.FlowEnvironment.FlowDataUpdateNotification(guid, newData); + context.FlowEnvironment.FlowDataNotification(guid, newData); } } - public static bool TryCreateInterruptTask(IDynamicContext context, NodeModelBase currentNode, out Task? task) - { - bool haveTask; - Console.WriteLine($"[{currentNode.MethodDetails.MethodName}]在当前分支中断"); - - if (currentNode.DebugSetting.InterruptClass == InterruptClass.None) - { - haveTask = false; - task = null; - } - else if (currentNode.DebugSetting.InterruptClass == InterruptClass.Branch) // 中断当前分支 - { - haveTask = true; - task = context.FlowEnvironment.ChannelFlowInterrupt.CreateChannelWithTimeoutAsync(currentNode.Guid, TimeSpan.FromSeconds(60 * 30)); // 中断30分钟 - } - else - { - haveTask = false; - task = null; - } - - return haveTask; - } - - /// /// 释放对象 /// diff --git a/NodeFlow/FlowEnvironment.cs b/NodeFlow/FlowEnvironment.cs index 5871b63..1f8427d 100644 --- a/NodeFlow/FlowEnvironment.cs +++ b/NodeFlow/FlowEnvironment.cs @@ -11,6 +11,7 @@ using Serein.NodeFlow.Tool; using System.Collections.Concurrent; using System.Reflection; using System.Xml.Linq; +using static Serein.Library.Utils.ChannelFlowInterrupt; using static Serein.NodeFlow.FlowStarter; namespace Serein.NodeFlow @@ -57,7 +58,7 @@ namespace Serein.NodeFlow /// /// 节点的命名空间 /// - public const string NodeSpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; + public const string SpaceName = $"{nameof(Serein)}.{nameof(Serein.NodeFlow)}.{nameof(Serein.NodeFlow.Model)}"; #region 环境接口事件 /// @@ -112,16 +113,21 @@ namespace Serein.NodeFlow #endregion + /// + /// 环境名称 + /// + public string EnvName { get; set; } = SpaceName; + + /// + /// 是否全局中断 + /// + public bool IsGlobalInterrupt { get; set; } /// /// 流程中断器 /// public ChannelFlowInterrupt ChannelFlowInterrupt { get; set; } - /// - /// 是否全局中断 - /// - public bool IsGlobalInterrupt { get; set; } /// /// 存储加载的程序集路径 @@ -466,7 +472,8 @@ namespace Serein.NodeFlow /// public void RemoteNode(string nodeGuid) { - NodeModelBase remoteNode = GuidToModel(nodeGuid); + var remoteNode = GuidToModel(nodeGuid); + if (remoteNode is null) return; if (remoteNode.IsStart) { return; @@ -522,8 +529,10 @@ namespace Serein.NodeFlow public void ConnectNode(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) { // 获取起始节点与目标节点 - NodeModelBase fromNode = GuidToModel(fromNodeGuid); - NodeModelBase toNode = GuidToModel(toNodeGuid); + var fromNode = GuidToModel(fromNodeGuid); + var toNode = GuidToModel(toNodeGuid); + if (fromNode is null) return; + if (toNode is null) return; // 开始连接 ConnectNode(fromNode, toNode, connectionType); // 外部调用连接方法 @@ -539,8 +548,10 @@ namespace Serein.NodeFlow public void RemoteConnect(string fromNodeGuid, string toNodeGuid, ConnectionType connectionType) { // 获取起始节点与目标节点 - NodeModelBase fromNode = GuidToModel(fromNodeGuid); - NodeModelBase toNode = GuidToModel(toNodeGuid); + var fromNode = GuidToModel(fromNodeGuid); + var toNode = GuidToModel(toNodeGuid); + if (fromNode is null) return; + if (toNode is null) return; RemoteConnect(fromNode, toNode, connectionType); //fromNode.SuccessorNodes[connectionType].Remove(toNode); @@ -606,29 +617,9 @@ namespace Serein.NodeFlow /// public void SetStartNode(string newNodeGuid) { - NodeModelBase newStartNodeModel = GuidToModel(newNodeGuid); + var newStartNodeModel = GuidToModel(newNodeGuid); + if (newStartNodeModel is null) return; SetStartNode(newStartNodeModel); - - //if (string.IsNullOrEmpty(newNodeGuid)) - //{ - // return; - //} - //if (Nodes.TryGetValue(newNodeGuid, out NodeModelBase? newStartNodeModel)) - //{ - // if (newStartNodeModel != null) - // { - // SetStartNode(newStartNodeModel); - // //var oldNodeGuid = ""; - // //if(StartNode != null) - // //{ - // // oldNodeGuid = StartNode.Guid; - // // StartNode.IsStart = false; - // //} - // //newStartNodeModel.IsStart = true; - // //StartNode = newStartNodeModel; - // //OnStartNodeChange?.Invoke(new StartNodeChangeEventArgs(oldNodeGuid, newNodeGuid)); - // } - //} } /// @@ -637,22 +628,63 @@ namespace Serein.NodeFlow /// 被中断的目标节点Guid /// 中断级别 /// 操作是否成功 - public bool NodeInterruptChange(string nodeGuid, InterruptClass interruptClass) + public bool SetNodeInterrupt(string nodeGuid, InterruptClass interruptClass) { - NodeModelBase nodeModel = GuidToModel(nodeGuid); + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return false; + if (interruptClass == InterruptClass.None) + { + nodeModel.CancelInterrupt(); + } + else if (interruptClass == InterruptClass.Branch) + { + nodeModel.DebugSetting.CancelInterruptCallback?.Invoke(); + nodeModel.DebugSetting.GetInterruptTask = () => + { + //ChannelFlowInterrupt.EnableDiscardMode(nodeGuid,true); + return ChannelFlowInterrupt.GetOrCreateChannelAsync(nodeGuid); + }; + nodeModel.DebugSetting.CancelInterruptCallback = () => + { + ChannelFlowInterrupt.TriggerSignal(nodeGuid); + //ChannelFlowInterrupt.EnableDiscardMode(nodeGuid, false); + }; + + } + else if (interruptClass == InterruptClass.Global) // 全局……做不了omg + { + return false; + } nodeModel.DebugSetting.InterruptClass = interruptClass; OnNodeInterruptStateChange.Invoke(new NodeInterruptStateChangeEventArgs(nodeGuid, interruptClass)); return true; - } + public bool AddInterruptExpression(string nodeGuid, string expression) + { + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return false; + if (nodeModel.DebugSetting.InterruptExpressions.Contains(expression)) + { + Console.WriteLine("表达式已存在"); + return false; + } + else + { + nodeModel.DebugSetting.InterruptExpressions.Add(expression); + return true; + } + } + + /// /// 监视节点的数据 /// /// 需要监视的节点Guid public void SetNodeFLowDataMonitorState(string nodeGuid, bool isMonitor) { - NodeModelBase nodeModel = GuidToModel(nodeGuid); + var nodeModel = GuidToModel(nodeGuid); + if (nodeModel is null) return; nodeModel.DebugSetting.IsMonitorFlowData = isMonitor; } @@ -660,29 +692,51 @@ namespace Serein.NodeFlow /// 节点数据更新通知 /// /// - public void FlowDataUpdateNotification(string nodeGuid, object flowData) + public void FlowDataNotification(string nodeGuid, object flowData) { OnMonitorObjectChange?.Invoke(new MonitorObjectEventArgs(nodeGuid, flowData)); } + + public Task GetOrCreateGlobalInterruptAsync() + { + IsGlobalInterrupt = true; + return ChannelFlowInterrupt.GetOrCreateChannelAsync(this.EnvName); + } + + + + + + + + + + + + + /// /// Guid 转 NodeModel /// /// 节点Guid /// 节点Model /// 无法获取节点、Guid/节点为null时报错 - private NodeModelBase GuidToModel(string nodeGuid) + private NodeModelBase? GuidToModel(string nodeGuid) { if (string.IsNullOrEmpty(nodeGuid)) { - throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid)); + //throw new ArgumentNullException("not contains - Guid没有对应节点:" + (nodeGuid)); + return null; } if (!Nodes.TryGetValue(nodeGuid, out NodeModelBase? nodeModel) || nodeModel is null) { - throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid)); + //throw new ArgumentNullException("null - Guid存在对应节点,但节点为null:" + (nodeGuid)); + return null; } return nodeModel; } + #endregion #region 私有方法 diff --git a/NodeFlow/FlowStarter.cs b/NodeFlow/FlowStarter.cs index 9737f0a..ef60073 100644 --- a/NodeFlow/FlowStarter.cs +++ b/NodeFlow/FlowStarter.cs @@ -303,20 +303,22 @@ namespace Serein.NodeFlow while (!_flipFlopCts.IsCancellationRequested) { - var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); - await NodeModelBase.FlowRefreshDataOrInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据 + var newFlowData = await singleFlipFlopNode.ExecutingAsync(context); // 获取触发器等待Task + await NodeModelBase.RefreshFlowDataAndExpInterrupt(context, singleFlipFlopNode, newFlowData); // 全局触发器触发后刷新该触发器的节点数据 if (singleFlipFlopNode.NextOrientation != ConnectionType.None) { var nextNodes = singleFlipFlopNode.SuccessorNodes[singleFlipFlopNode.NextOrientation]; for (int i = nextNodes.Count - 1; i >= 0 && !_flipFlopCts.IsCancellationRequested; i--) { - if (nextNodes[i].DebugSetting.IsEnable) // 排除未启用的后继节点 + // 筛选出启用的节点、未被中断的节点 + if (nextNodes[i].DebugSetting.IsEnable && nextNodes[i].DebugSetting.InterruptClass == InterruptClass.None) { nextNodes[i].PreviousNode = singleFlipFlopNode; await nextNodes[i].StartExecute(context); // 启动执行触发器后继分支的节点 } } } + } } catch (Exception ex) diff --git a/NodeFlow/Model/SingleFlipflopNode.cs b/NodeFlow/Model/SingleFlipflopNode.cs index 983905e..03d0ba8 100644 --- a/NodeFlow/Model/SingleFlipflopNode.cs +++ b/NodeFlow/Model/SingleFlipflopNode.cs @@ -22,12 +22,16 @@ namespace Serein.NodeFlow.Model public override async Task ExecutingAsync(IDynamicContext context) { #region 执行前中断 - if (DebugSetting.InterruptClass != InterruptClass.None && TryCreateInterruptTask(context, this, out Task? task)) // 执行触发前 + if (DebugSetting.InterruptClass != InterruptClass.None) // 执行触发前 { string guid = this.Guid.ToString(); - this.CancelInterruptCallback ??= () => context.FlowEnvironment.ChannelFlowInterrupt.TriggerSignal(guid); - var cancelType = await task!; - await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{(cancelType == CancelType.Manual ? "手动取消" : "自动取消")},开始执行后继分支"); + var cancelType = await this.DebugSetting.GetInterruptTask(); + //if (cancelType == CancelType.Discard) + //{ + // this.NextOrientation = ConnectionType.None; + // return null; + //} + await Console.Out.WriteLineAsync($"[{this.MethodDetails.MethodName}]中断已{cancelType},开始执行后继分支"); } #endregion diff --git a/WorkBench/MainWindow.xaml.cs b/WorkBench/MainWindow.xaml.cs index 042f9b8..850883e 100644 --- a/WorkBench/MainWindow.xaml.cs +++ b/WorkBench/MainWindow.xaml.cs @@ -144,19 +144,21 @@ namespace Serein.WorkBench public MainWindow() { + InitializeComponent(); + ViewModel = new MainWindowViewModel(this); FlowEnvironment = ViewModel.FlowEnvironment; - InitFlowEvent(); + ObjectViewer.FlowEnvironment = FlowEnvironment; - InitializeComponent(); + InitFlowEnvironmentEvent(); // 配置环境事件 + logWindow = new LogWindow(); logWindow.Show(); // 重定向 Console 输出 - var logTextWriter = new LogTextWriter(WriteLog,() => logWindow.Clear());; + var logTextWriter = new LogTextWriter(msg => logWindow.AppendText(msg), () => logWindow.Clear());; Console.SetOut(logTextWriter); - - InitUI(); + InitCanvasUI(); var project = App.FlowProjectData; if (project == null) @@ -164,12 +166,10 @@ namespace Serein.WorkBench return; } InitializeCanvas(project.Basic.Canvas.Width, project.Basic.Canvas.Lenght);// 设置画布大小 - - FlowEnvironment.LoadProject(project, App.FileDataPath); // 加载项目 } - private void InitFlowEvent() + private void InitFlowEnvironmentEvent() { FlowEnvironment.OnDllLoad += FlowEnvironment_DllLoadEvent; // FlowEnvironment.OnLoadNode += FlowEnvironment_NodeLoadEvent; @@ -189,7 +189,7 @@ namespace Serein.WorkBench - private void InitUI() + private void InitCanvasUI() { canvasTransformGroup = new TransformGroup(); scaleTransform = new ScaleTransform(); @@ -239,10 +239,7 @@ namespace Serein.WorkBench } #endregion - public void WriteLog(string message) - { - logWindow.AppendText(message); - } + #region 运行环境事件 /// @@ -265,7 +262,7 @@ namespace Serein.WorkBench /// private void FlowEnvironment_OnFlowRunComplete(FlowEventArgs eventArgs) { - WriteLog("-------运行完成---------\r\n"); + Console.WriteLine("-------运行完成---------\r\n"); } /// @@ -462,20 +459,30 @@ namespace Serein.WorkBench private void FlowEnvironment_OnMonitorObjectChange(MonitorObjectEventArgs eventArgs) { string nodeGuid = eventArgs.NodeGuid; - if (string.IsNullOrEmpty(ObjectViewer.NodeGuid)) // 如果没有加载过 - { - ObjectViewer.NodeGuid = nodeGuid; - ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点 - } - else - { - // 加载过,如果显示的对象来源并非同一个节点,则停止监听之前的节点 - if (!ObjectViewer.NodeGuid.Equals(nodeGuid)) + + ObjectViewer.Dispatcher.BeginInvoke(() => { + if (string.IsNullOrEmpty(ObjectViewer.NodeGuid)) // 如果没有加载过 { - FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false); + ObjectViewer.NodeGuid = nodeGuid; + ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点 } - ObjectViewer.RefreshObjectTree(eventArgs.NewData); - } + else + { + // 加载过,如果显示的对象来源并非同一个节点,则停止监听之前的节点 + if (!ObjectViewer.NodeGuid.Equals(nodeGuid)) + { + FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false); + ObjectViewer.NodeGuid = nodeGuid; + ObjectViewer.LoadObjectInformation(eventArgs.NewData); // 加载节点 + } + else + { + ObjectViewer.RefreshObjectTree(eventArgs.NewData); + } + } + + }); + } @@ -497,7 +504,6 @@ namespace Serein.WorkBench nodeControl.ViewModel.IsInterrupt = true; } - } /// @@ -509,7 +515,6 @@ namespace Serein.WorkBench { string nodeGuid = eventArgs.NodeGuid; NodeControlBase nodeControl = GuidToControl(nodeGuid); - Console.WriteLine("节点触发了中断"); } @@ -579,7 +584,7 @@ namespace Serein.WorkBench var childNodeControl = CreateNodeControlOfNodeInfo(childNode, md); if (childNodeControl == null) { - WriteLog($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n"); + Console.WriteLine($"无法为节点类型创建节点控件: {childNode.MethodName}\r\n"); continue; } @@ -687,13 +692,13 @@ namespace Serein.WorkBench { if (nodeControl?.ViewModel?.Node?.DebugSetting?.InterruptClass == InterruptClass.None) { - FlowEnvironment.NodeInterruptChange(nodeGuid, InterruptClass.Branch); + FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.Branch); menuItem.Header = "取消中断"; } else { - FlowEnvironment.NodeInterruptChange(nodeGuid, InterruptClass.None); + FlowEnvironment.SetNodeInterrupt(nodeGuid, InterruptClass.None); menuItem.Header = "在此中断"; } @@ -707,6 +712,8 @@ namespace Serein.WorkBench var node = nodeControl?.ViewModel?.Node; if(node is not null) { + FlowEnvironment.SetNodeFLowDataMonitorState(ObjectViewer.NodeGuid, false); // 通知环境,该节点的数据更新后需要传到UI + ObjectViewer.NodeGuid = node.Guid; FlowEnvironment.SetNodeFLowDataMonitorState(node.Guid, true); // 通知环境,该节点的数据更新后需要传到UI } @@ -2533,6 +2540,23 @@ namespace Serein.WorkBench break; } + // 计算角落 + //switch (localhost) + //{ + // case Localhost.Right: + // point = new Point(0, element.ActualHeight / 2); // 左边中心 + // break; + // case Localhost.Left: + // point = new Point(element.ActualWidth, element.ActualHeight / 2); // 右边中心 + // break; + // case Localhost.Bottom: + // point = new Point(element.ActualWidth / 2, 0); // 上边中心 + // break; + // case Localhost.Top: + // point = new Point(element.ActualWidth / 2, element.ActualHeight); // 下边中心 + // break; + //} + // 将相对控件的坐标转换到画布中的全局坐标 return element.TranslatePoint(point, canvas); } diff --git a/WorkBench/Node/NodeControlViewModelBase.cs b/WorkBench/Node/NodeControlViewModelBase.cs index 191ec64..dad81e6 100644 --- a/WorkBench/Node/NodeControlViewModelBase.cs +++ b/WorkBench/Node/NodeControlViewModelBase.cs @@ -49,7 +49,7 @@ namespace Serein.WorkBench.Node.ViewModel if (value != null) { Node.DebugSetting = value; - OnPropertyChanged(nameof(DebugSetting)); + OnPropertyChanged(/*nameof(DebugSetting)*/); } } } @@ -62,7 +62,7 @@ namespace Serein.WorkBench.Node.ViewModel if(value != null) { Node.MethodDetails = value; - OnPropertyChanged(nameof(MethodDetails)); + OnPropertyChanged(/*nameof(MethodDetails)*/); } } } @@ -74,7 +74,7 @@ namespace Serein.WorkBench.Node.ViewModel set { isInterrupt = value; - OnPropertyChanged(nameof(IsInterrupt)); + OnPropertyChanged(/*nameof(IsInterrupt)*/); } } diff --git a/WorkBench/Node/View/ActionNodeControl.xaml b/WorkBench/Node/View/ActionNodeControl.xaml index b6c1ae5..72aac64 100644 --- a/WorkBench/Node/View/ActionNodeControl.xaml +++ b/WorkBench/Node/View/ActionNodeControl.xaml @@ -24,7 +24,6 @@ - diff --git a/WorkBench/Themes/ObjectViewerControl.xaml b/WorkBench/Themes/ObjectViewerControl.xaml index ab28307..928a8cb 100644 --- a/WorkBench/Themes/ObjectViewerControl.xaml +++ b/WorkBench/Themes/ObjectViewerControl.xaml @@ -8,7 +8,7 @@ d:DesignHeight="450" d:DesignWidth="800"> - + @@ -19,11 +19,11 @@ Click="TimerRefreshButton_Click"--> -