流程上下文添加了调用信息记录

This commit is contained in:
fengjiayi
2025-07-28 17:38:51 +08:00
parent ccb8e49abc
commit 74961fa2c4
22 changed files with 480 additions and 144 deletions

View File

@@ -2,6 +2,9 @@
using Serein.Library.Utils;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
namespace Serein.Library.Api
@@ -11,6 +14,12 @@ namespace Serein.Library.Api
/// </summary>
public interface IFlowContext
{
/// <summary>
/// 是否记录流程信息
/// </summary>
bool IsRecordInvokeInfo { get; set; }
/// <summary>
/// 标识流程
/// </summary>
@@ -39,6 +48,19 @@ namespace Serein.Library.Api
Exception ExceptionOfRuning { get; set; }
/// <summary>
/// 获取当前流程上下文的所有节点调用信息,包含每个节点的执行时间、调用类型、执行状态等。
/// </summary>
/// <returns></returns>
List<FlowInvokeInfo> GetAllInvokeInfos();
/// <summary>
/// 新增当前流程上下文的节点调用信息。
/// </summary>
FlowInvokeInfo NewInvokeInfo(IFlowNode previousNode, IFlowNode theNode, FlowInvokeInfo.InvokeType invokeType);
/// <summary>
/// 获取节点的运行时参数数据
/// </summary>
@@ -107,5 +129,176 @@ namespace Serein.Library.Api
/// 用以提前结束当前上下文流程的运行
/// </summary>
void Exit();
}
}
/// <summary>
/// 流程调用信息,记录每个节点的调用信息,包括执行时间、调用类型、执行状态等。
/// </summary>
public sealed class FlowInvokeInfo
{
/// <summary>
/// 调用类型枚举,指示节点的调用方式。
/// </summary>
public enum InvokeType
{
/// <summary>
/// 初始化。
/// </summary>
None = -1,
/// <summary>
/// 上游分支调用,指向上游流程的节点。
/// </summary>
Upstream = 0,
/// <summary>
/// 真分支调用,指示节点执行成功后的分支。
/// </summary>
IsSucceed = 1,
/// <summary>
/// 假分支调用,指示节点执行失败后的分支。
/// </summary>
IsFail = 2,
/// <summary>
/// 异常发生分支调用,指示节点执行过程中发生异常后的分支。
/// </summary>
IsError = 3,
/// <summary>
/// 参数来源调用,标明此次调用出自其它节点需要参数时的执行。
/// </summary>
ArgSource = 4,
}
/// <summary>
/// 运行状态枚举,指示节点的执行结果。
/// </summary>
public enum RunState
{
/// <summary>
/// 初始化
/// </summary>
None = -1,
/// <summary>
/// 正在运行,指示节点正在执行中。
/// </summary>
Running = 0,
/// <summary>
/// 执行成功,指示节点执行完成且结果正常。
/// </summary>
Succeed = 1,
/// <summary>
/// 执行失败,指示节点执行完成但结果异常或不符合预期。
/// </summary>
Failed = 2,
/// <summary>
/// 执行异常,指示节点在执行过程中发生了未处理的异常。
/// </summary>
Error = 3,
}
/// <summary>
/// 流程上下文标识符,唯一标识一个流程上下文
/// </summary>
public string ContextGuid { get; set; }
/// <summary>
/// 节点执行信息标识符,唯一标识一个节点执行信息
/// </summary>
public long Id { get; set; }
/// <summary>
/// 上一节点的唯一标识符,指向流程图中的上一个节点
/// </summary>
public string PreviousNodeGuid { get; set; }
/// <summary>
/// 节点唯一标识符,指向流程图中的节点
/// </summary>
public string NodeGuid { get; set; }
/// <summary>
/// 执行时间,记录节点执行的时间戳
/// </summary>
public DateTime StateTime { get; } = DateTime.Now;
/// <summary>
/// 节点调用类型,指示节点的调用方式
/// </summary>
public InvokeType Type { get; set; }
/// <summary>
/// 节点方法名称,指示节点执行的方法
/// </summary>
public string Method { get; set; }
/// <summary>
/// 节点执行状态,指示节点的执行结果
/// </summary>
public RunState State { get; set; }
/// <summary>
/// 耗时,记录节点执行的耗时(单位:毫秒)
/// </summary>
public TimeSpan TS { get; private set; } = TimeSpan.Zero;
/// <summary>
/// 节点参数,存储节点执行时的参数信息
/// </summary>
public string[] Parameters { get; private set; }
/// <summary>
/// 节点执行结果,存储节点执行后的结果信息
/// </summary>
public string Result { get; private set; }
public void UploadState(RunState runState)
{
State = runState;
TS = DateTime.Now - StateTime;
}
public void UploadResultValue(object value = null)
{
if(value is null)
{
Result = string.Empty;
}
else
{
Result = value.ToString();
}
}
public void UploadParameters(object[] values = null)
{
if (values is null)
{
Parameters = [];
}
else
{
Parameters = values.Select(v => v.ToString()).ToArray();
}
}
public override string ToString()
{
return $"[{State}]{TS.TotalSeconds:0.000}ms : {Result}";
}
}
}

View File

@@ -414,10 +414,11 @@ namespace Serein.Library.Api
/// </summary>
public class NodeTakeOutEventArgs : FlowEventArgs
{
public NodeTakeOutEventArgs(string canvasGuid, string nodeGuid)
public NodeTakeOutEventArgs(string canvasGuid, string containerNodeGuid, string nodeGuid)
{
CanvasGuid = canvasGuid;
NodeGuid = nodeGuid;
ContainerNodeGuid = containerNodeGuid;
}
public string CanvasGuid { get; }
@@ -426,6 +427,11 @@ namespace Serein.Library.Api
/// 需要取出的节点Guid
/// </summary>
public string NodeGuid { get; private set; }
/// <summary>
/// 容器节点Guid
/// </summary>
public string ContainerNodeGuid { get; private set; }
}

View File

@@ -11,6 +11,8 @@ namespace Serein.Library.Api
/// </summary>
public interface INodeContainer
{
string Guid { get; }
/// <summary>
/// 放置一个节点
/// </summary>

View File

@@ -208,7 +208,9 @@ namespace Serein.Library
Stack<IFlowNode> stack = new Stack<IFlowNode>();
HashSet<IFlowNode> processedNodes = new HashSet<IFlowNode>(); // 用于记录已处理上游节点的节点
stack.Push(nodeModel);
#nullable enable
IFlowNode? previousNode = null;
IFlowNode? currentNode = null;
while (true)
{
if (token.IsCancellationRequested)
@@ -218,9 +220,30 @@ namespace Serein.Library
#region
// 从栈中弹出一个节点作为当前节点进行处理
var currentNode = stack.Pop();
previousNode = currentNode;
currentNode = stack.Pop();
#region
FlowInvokeInfo? invokeInfo = null;
var isRecordInvokeInfo = context.IsRecordInvokeInfo;
if (!isRecordInvokeInfo) goto Label_NotRecordInvoke;
FlowInvokeInfo.InvokeType invokeType = context.NextOrientation switch
{
ConnectionInvokeType.IsSucceed => FlowInvokeInfo.InvokeType.IsSucceed,
ConnectionInvokeType.IsFail => FlowInvokeInfo.InvokeType.IsFail,
ConnectionInvokeType.IsError => FlowInvokeInfo.InvokeType.IsError,
ConnectionInvokeType.Upstream => FlowInvokeInfo.InvokeType.Upstream,
_ => FlowInvokeInfo.InvokeType.None
};
invokeInfo = context.NewInvokeInfo(previousNode, currentNode, invokeType);
#endregion
Label_NotRecordInvoke:
context.NextOrientation = ConnectionInvokeType.None; // 重置上下文状态
FlowResult flowResult = null;
FlowResult flowResult = null;
try
{
flowResult = await currentNode.ExecutingAsync(context, token);
@@ -239,6 +262,22 @@ namespace Serein.Library
}
#endregion
#region
var state = context.NextOrientation switch
{
ConnectionInvokeType.IsFail => FlowInvokeInfo.RunState.Failed,
ConnectionInvokeType.IsError => FlowInvokeInfo.RunState.Error,
_ => FlowInvokeInfo.RunState.Succeed
};
if (isRecordInvokeInfo)
{
invokeInfo.UploadState(state);
invokeInfo.UploadResultValue(flowResult.Value);
}
#endregion
#region
context.AddOrUpdateFlowData(currentNode.Guid, flowResult); // 上下文中更新数据
@@ -289,10 +328,18 @@ namespace Serein.Library
#endregion
#if DEBUG
await Task.Delay(1);
//await Task.Delay(1);
#endif
}
}
/// <summary>
/// 获取所有参数
/// </summary>
/// <param name="nodeModel"></param>
/// <param name="context"></param>
/// <param name="token"></param>
/// <returns></returns>
public static async Task<object[]> GetParametersAsync(this IFlowNode nodeModel, IFlowContext context, CancellationToken token)
{
var md = nodeModel.MethodDetails;
@@ -437,6 +484,7 @@ namespace Serein.Library
}
var context = new FlowContext(flowCallNode.Env);
for (int index = 0; index < pds.Length; index++)
{
ParameterDetails pd = pds[index];

View File

@@ -1,8 +1,13 @@
using Serein.Library.Api;
using Microsoft.VisualBasic;
using Serein.Library.Api;
using Serein.Library.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace Serein.Library
{
@@ -24,6 +29,11 @@ namespace Serein.Library
}
private string _guid = global::System.Guid.NewGuid().ToString();
/// <summary>
/// 是否记录流程调用信息
/// </summary>
public bool IsRecordInvokeInfo { get; set; } = true;
string IFlowContext.Guid => _guid;
/// <summary>
@@ -36,7 +46,6 @@ namespace Serein.Library
/// </summary>
public RunState RunState { get; set; } = RunState.NoStart;
/// <summary>
/// 当前节点执行完成后,设置该属性,让运行环境判断接下来要执行哪个分支的节点。
/// </summary>
@@ -66,27 +75,43 @@ namespace Serein.Library
/// 记录节点的运行时参数数据
/// </summary>
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, object>> dictNodeParams = new ConcurrentDictionary<string, ConcurrentDictionary<int, object>>();
/*
/// <summary>
/// 每个流程上下文分别存放节点的当前数据
/// </summary>
private readonly ConcurrentDictionary<IFlowNode, FlowResult> dictNodeFlowData = new ConcurrentDictionary<IFlowNode, FlowResult>();
/// <summary>
/// 每个流程上下文存储运行时节点的调用关系
/// 记录流程调用信息
/// </summary>
private readonly ConcurrentDictionary<IFlowNode, IFlowNode> dictPreviousNodes = new ConcurrentDictionary<IFlowNode, IFlowNode>();
//private Dictionary<long, IFlowNode> flowInvokeNodes = new Dictionary<long, IFlowNode>();
private Dictionary<long, FlowInvokeInfo> flowInvokeInfos = new Dictionary<long, FlowInvokeInfo>();
private static long _idCounter = 0;
/// <summary>
/// 记录忽略处理的流程
/// 在执行方法之前,获取新的调用信息
/// </summary>
private readonly ConcurrentDictionary<IFlowNode, bool> dictIgnoreNodeFlow = new ConcurrentDictionary<IFlowNode, bool>();
/// <param name="previousNode">上一个节点</param>
/// <param name="theNode">执行节点</param>
public FlowInvokeInfo NewInvokeInfo(IFlowNode previousNode, IFlowNode theNode, FlowInvokeInfo.InvokeType invokeType)
{
//Interlocked
var id = Interlocked.Increment(ref _idCounter);
/// <summary>
/// 记录节点的运行时参数数据
/// </summary>
private readonly ConcurrentDictionary<IFlowNode, ConcurrentDictionary<int, object>> dictNodeParams = new ConcurrentDictionary<IFlowNode, ConcurrentDictionary<int, object>>();*/
FlowInvokeInfo flowInvokeInfo = new FlowInvokeInfo
{
ContextGuid = this._guid,
Id = id,
PreviousNodeGuid = previousNode?.Guid,
Method = theNode.MethodDetails?.MethodName,
NodeGuid = theNode.Guid,
Type = invokeType,
State = FlowInvokeInfo.RunState.None,
};
flowInvokeInfos.Add(id, flowInvokeInfo);
return flowInvokeInfo;
}
public List<FlowInvokeInfo> GetAllInvokeInfos() => [.. flowInvokeInfos.Values];
/// <summary>
/// 设置节点的运行时参数数据
/// </summary>
@@ -138,8 +163,6 @@ namespace Serein.Library
}
}
/// <summary>
/// 设置运行时上一节点
/// </summary>
@@ -178,7 +201,6 @@ namespace Serein.Library
dictIgnoreNodeFlow.AddOrUpdate(node, (o) => false, (o, n) => false);
}
/// <summary>
/// 获取当前节点的运行时上一节点
/// </summary>
@@ -252,67 +274,38 @@ namespace Serein.Library
throw new InvalidOperationException($"透传{nodeModel}节点数据时发生异常:上一节点不存在数据");
}
/// <summary>
/// 开始
/// </summary>
/// <summary>
/// 重置
/// </summary>
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;
flowInvokeInfos.Clear();
_guid = global::System.Guid.NewGuid().ToString();
}
/// <summary>
/// 结束当前流程上下文
/// </summary>
public void Exit()
{
//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.Completion;
_guid = global::System.Guid.NewGuid().ToString();
}
/// <summary>
/// 释放当前上下文中的所有资源
/// </summary>
/// <param name="keyValuePairs"></param>
private void Dispose(ref IDictionary<string, object> keyValuePairs)
{
foreach (var nodeObj in keyValuePairs.Values)
@@ -370,7 +363,6 @@ namespace Serein.Library
list.Clear();
}
private void Dispose(ref IList<object> list)
{
foreach (var nodeObj in list)

View File

@@ -398,7 +398,6 @@ namespace Serein.Library
CancellationTokenSource cts = new CancellationTokenSource();
FlowResult flowResult;
#if DEBUG
flowResult = await BenchmarkHelpers.BenchmarkAsync(async () =>
{
var node = flowCallTree.Get(startNodeGuid);

View File

@@ -249,11 +249,31 @@ namespace Serein.Library
sourceNode = sourceNodeTemp;
if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeData)
{
inputParameter = context.GetFlowData(sourceNode.Guid)?.Value;
}
else if (ArgDataSourceType == ConnectionArgSourceType.GetOtherNodeDataOfInvoke) // 立刻执行目标节点获取参数
inputParameter = (await sourceNode.ExecutingAsync(context, CancellationToken.None)).Value;
{
if (context.IsRecordInvokeInfo)
{
var invokeInfo = context.NewInvokeInfo(NodeModel, sourceNode, FlowInvokeInfo.InvokeType.ArgSource);
var result = await sourceNode.ExecutingAsync(context, CancellationToken.None);
inputParameter = result.Value;
invokeInfo.UploadResultValue(result.Value);
invokeInfo.UploadState(FlowInvokeInfo.RunState.Succeed);
}
else
{
var result = await sourceNode.ExecutingAsync(context, CancellationToken.None);
inputParameter = result.Value;
}
}
else
{
throw new Exception("无效的 ArgDataSourceType");
}
}
// 5. 表达式处理

View File

@@ -37,11 +37,11 @@ namespace Serein.Library.Utils
double avg = total / count;
Console.WriteLine($"运行 {count} 次:");
Console.WriteLine($"总耗时 :{total} 毫秒:");
Console.WriteLine($"最大耗时:{max} 毫秒");
Console.WriteLine($"最小耗时:{min} 毫秒");
Console.WriteLine($"平均耗时:{avg} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:");
SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:");
SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒");
}
/// <summary>
@@ -68,11 +68,11 @@ namespace Serein.Library.Utils
}
double avg = total / count;
Console.WriteLine($"运行 {count} 次:");
Console.WriteLine($"总耗时 :{total} 毫秒:");
Console.WriteLine($"最大耗时:{max} 毫秒");
Console.WriteLine($"最小耗时:{min} 毫秒");
Console.WriteLine($"平均耗时:{avg} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:");
SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:");
SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒");
}
/// <summary>
@@ -100,11 +100,11 @@ namespace Serein.Library.Utils
}
double avg = total / count;
Console.WriteLine($"运行 {count} 次:");
Console.WriteLine($"总耗时 :{total} 毫秒:");
Console.WriteLine($"最大耗时:{max} 毫秒");
Console.WriteLine($"最小耗时:{min} 毫秒");
Console.WriteLine($"平均耗时:{avg} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"运行 {count} 次:");
SereinEnv.WriteLine(InfoType.INFO, $"总耗时 :{total} 毫秒:");
SereinEnv.WriteLine(InfoType.INFO, $"最大耗时:{max} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"最小耗时:{min} 毫秒");
SereinEnv.WriteLine(InfoType.INFO, $"平均耗时:{avg} 毫秒");
return result;
}
@@ -127,8 +127,8 @@ namespace Serein.Library.Utils
if (ms < min) min = ms;
total += ms;
Console.WriteLine($"运行1次耗时 :{total} 毫秒:");
Debug.WriteLine($"运行1次耗时 :{total} 毫秒:");
var tips = $"运行耗时 :{total} 毫秒:";
SereinEnv.WriteLine(InfoType.INFO, tips);
return result;
}
}