添加 MX-PD-盘古 项目文件
将 MX-PD-盘古 - new 目录下的所有文件添加到主仓库
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
public class ActivityControl
|
||||
{
|
||||
public CancellationToken CancellationToken { get; }
|
||||
private readonly Func<Task> _pauseCheck;
|
||||
|
||||
public ActivityControl(CancellationToken cancellationToken, Func<Task> pauseCheck)
|
||||
{
|
||||
CancellationToken = cancellationToken;
|
||||
_pauseCheck = pauseCheck ?? throw new ArgumentNullException(nameof(pauseCheck));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有取消请求,如果有则抛出 OperationCanceledException。
|
||||
/// </summary>
|
||||
public void ThrowIfCancellationRequested()
|
||||
{
|
||||
CancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查流程是否处于暂停状态,如果是则等待直到恢复。
|
||||
/// 同时响应取消请求。
|
||||
/// </summary>
|
||||
public Task CheckPauseAsync() => _pauseCheck();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
public enum ActivityResult
|
||||
{
|
||||
Success,
|
||||
Failure,
|
||||
Canceled,
|
||||
}
|
||||
}
|
||||
111
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/AsyncEvent.cs
Normal file
111
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/AsyncEvent.cs
Normal file
@@ -0,0 +1,111 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 一个异步事件同步原语,类似于 ManualResetEvent,但能够传递数据。
|
||||
/// 可以等待事件发生并获取数据,也可以将事件设置为已发生状态并附带数据。
|
||||
/// 默认情况下,事件在 Set 后保持已设置状态,直到 Reset 被调用。
|
||||
/// </summary>
|
||||
/// <typeparam name="T">事件发生时要传递的数据类型。</typeparam>
|
||||
public class AsyncEvent<T>
|
||||
{
|
||||
// 使用 volatile 确保 _tcs 的读写操作对所有线程可见
|
||||
private volatile TaskCompletionSource<T> _tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
private readonly object _lock = new object(); // 用于保护 _tcs 的替换操作
|
||||
|
||||
/// <summary>
|
||||
/// 获取一个值,指示事件是否已设置。
|
||||
/// </summary>
|
||||
public bool IsSet => _tcs.Task.IsCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// 等待事件被设置为已发生状态,并获取传递的数据。
|
||||
/// 如果事件已经设置,则立即返回数据。
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">用于取消等待的令牌。</param>
|
||||
/// <returns>事件发生时传递的数据。</returns>
|
||||
/// <exception cref="OperationCanceledException">如果在等待过程中取消令牌被请求。</exception>
|
||||
public async Task<T> WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Task<T> currentTask;
|
||||
lock (_lock)
|
||||
{
|
||||
currentTask = _tcs.Task;
|
||||
}
|
||||
|
||||
// 如果已经设置,直接返回结果
|
||||
if (currentTask.IsCompleted)
|
||||
{
|
||||
return await currentTask;
|
||||
}
|
||||
|
||||
// 注册取消回调,确保在取消时 tcs 被取消
|
||||
CancellationTokenRegistration? registration = null;
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
// 注意:这里需要确保在 TCS 完成或取消后,回调能被注销,避免资源泄露。
|
||||
// 更好的做法是使用 CancellationToken.Register(Action) 返回的 IDisposable
|
||||
// 但考虑到 TaskCompletionSource 的生命周期,通常当 Task 完成或取消时,
|
||||
// 注册的回调也会在适当时候被清理。
|
||||
registration = cancellationToken.Register(() =>
|
||||
{
|
||||
// 使用 TrySetCanceled 避免在 Task 已经完成时抛出异常
|
||||
_tcs.TrySetCanceled(cancellationToken);
|
||||
});
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await currentTask;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 确保在等待结束后注销取消回调
|
||||
registration?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将事件设置为已发生状态,并传递指定的数据。
|
||||
/// 如果事件已经设置,此调用将被忽略。
|
||||
/// </summary>
|
||||
/// <param name="data">要传递的数据。</param>
|
||||
/// <returns>如果事件成功设置为已发生状态,则为 true;否则为 false。</returns>
|
||||
public bool Set(T data)
|
||||
{
|
||||
// TrySetResult 是线程安全的,如果已经设置则返回 false
|
||||
// RunContinuationsAsynchronously 选项确保回调在线程池上运行,不会阻塞Set的调用者
|
||||
bool result = _tcs.TrySetResult(data);
|
||||
if (result)
|
||||
{
|
||||
Console.WriteLine($"[AsyncEvent] Event set with data: {data}");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将事件重置为未设置状态。
|
||||
/// 允许事件再次被等待。
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
// 如果当前 TaskCompletionSource 尚未完成,则不应该替换它
|
||||
// 因为这可能导致正在等待的 Task 永远不会完成
|
||||
if (_tcs.Task.IsCompleted)
|
||||
{
|
||||
_tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Console.WriteLine("[AsyncEvent] Event reset.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("[AsyncEvent] Event is not yet completed, cannot reset.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/IActivity.cs
Normal file
16
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/IActivity.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
public interface IActivity
|
||||
{
|
||||
string Name { get; }
|
||||
// Task<ActivityResult> ExecuteAsync(WorkflowContext context, CancellationToken cancellationToken, Func<Task> pauseCheck);
|
||||
Task<ActivityResult> ExecuteAsync(WorkflowContext context, ActivityControl activityControl);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{D155CFA8-7C12-49F0-B31B-A25327A81E04}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Mw.WorkFlow</RootNamespace>
|
||||
<AssemblyName>Mw.WorkFlow</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\x64\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\x64\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ActivityControl.cs" />
|
||||
<Compile Include="ActivityResult.cs" />
|
||||
<Compile Include="AsyncEvent.cs" />
|
||||
<Compile Include="IActivity.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WorkflowContext.cs" />
|
||||
<Compile Include="WorkflowDefinition.cs" />
|
||||
<Compile Include="WorkflowEngine.cs" />
|
||||
<Compile Include="WorkflowState.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,33 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// 有关程序集的一般信息由以下
|
||||
// 控制。更改这些特性值可修改
|
||||
// 与程序集关联的信息。
|
||||
[assembly: AssemblyTitle("Mw.WorkFlow")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("HP Inc.")]
|
||||
[assembly: AssemblyProduct("Mw.WorkFlow")]
|
||||
[assembly: AssemblyCopyright("Copyright © HP Inc. 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// 将 ComVisible 设置为 false 会使此程序集中的类型
|
||||
//对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型
|
||||
//请将此类型的 ComVisible 特性设置为 true。
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
|
||||
[assembly: Guid("d155cfa8-7c12-49f0-b31b-a25327a81e04")]
|
||||
|
||||
// 程序集的版本信息由下列四个值组成:
|
||||
//
|
||||
// 主版本
|
||||
// 次版本
|
||||
// 生成号
|
||||
// 修订号
|
||||
//
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
public class WorkflowContext
|
||||
{
|
||||
private const string RouteKeyPrefix = "Route:";
|
||||
private readonly ConcurrentDictionary<string, object> _data = new ConcurrentDictionary<string, object>();
|
||||
|
||||
public object this[string key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_data.TryGetValue(key, out var value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
throw new KeyNotFoundException($"Key '{key}' not found in WorkflowContext.");
|
||||
}
|
||||
set
|
||||
{
|
||||
_data[key] = value;
|
||||
}
|
||||
}
|
||||
public void SetData<T>(string key, T value)
|
||||
{
|
||||
_data[key] = value;
|
||||
}
|
||||
public T GetData<T>(string key)
|
||||
{
|
||||
if (_data.TryGetValue(key, out var value) && value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
throw new KeyNotFoundException($"Key '{key}' not found or of incorrect type.");
|
||||
}
|
||||
public bool TryGetData<T>(string key, out T value)
|
||||
{
|
||||
if (_data.TryGetValue(key, out var obj) && obj is T typedValue)
|
||||
{
|
||||
value = typedValue;
|
||||
return true;
|
||||
}
|
||||
value = default;
|
||||
return false;
|
||||
}
|
||||
public bool ContainsKey(string key)
|
||||
{
|
||||
return _data.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void SetRoute<T>(string routeKey, T value)
|
||||
{
|
||||
SetData(BuildRouteKey(routeKey), value);
|
||||
}
|
||||
|
||||
public T GetRoute<T>(string routeKey)
|
||||
{
|
||||
return GetData<T>(BuildRouteKey(routeKey));
|
||||
}
|
||||
|
||||
public bool TryGetRoute<T>(string routeKey, out T value)
|
||||
{
|
||||
return TryGetData(BuildRouteKey(routeKey), out value);
|
||||
}
|
||||
|
||||
public bool ContainsRoute(string routeKey)
|
||||
{
|
||||
return ContainsKey(BuildRouteKey(routeKey));
|
||||
}
|
||||
|
||||
public void Clear() { _data.Clear(); }
|
||||
|
||||
public Dictionary<string, object> ToDictionary()
|
||||
{
|
||||
return new Dictionary<string, object>(_data);
|
||||
}
|
||||
|
||||
private static string BuildRouteKey(string routeKey)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(routeKey))
|
||||
{
|
||||
throw new ArgumentNullException(nameof(routeKey));
|
||||
}
|
||||
|
||||
return RouteKeyPrefix + routeKey;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义工作流的结构,包含所有活动及其跳转规则。
|
||||
/// </summary>
|
||||
public class WorkflowDefinition
|
||||
{
|
||||
private readonly Dictionary<string, WorkflowStep> _steps;
|
||||
public string InitialStepId { get; private set; }
|
||||
|
||||
public WorkflowDefinition(string initialStepId)
|
||||
{
|
||||
InitialStepId = initialStepId ?? throw new ArgumentNullException(nameof(initialStepId));
|
||||
_steps = new Dictionary<string, WorkflowStep>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个工作流步骤到定义中。
|
||||
/// </summary>
|
||||
/// <param name="step">要添加的步骤。</param>
|
||||
public void AddStep(WorkflowStep step)
|
||||
{
|
||||
if (step == null) throw new ArgumentNullException(nameof(step));
|
||||
if (_steps.ContainsKey(step.Id))
|
||||
{
|
||||
throw new ArgumentException($"工作流步骤ID '{step.Id}' 已存在。");
|
||||
}
|
||||
_steps.Add(step.Id, step);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据ID获取工作流步骤。
|
||||
/// </summary>
|
||||
/// <param name="stepId">步骤的唯一ID。</param>
|
||||
/// <returns>对应的 WorkflowStep。</returns>
|
||||
public WorkflowStep GetStep(string stepId)
|
||||
{
|
||||
if (_steps.TryGetValue(stepId, out var step))
|
||||
{
|
||||
return step;
|
||||
}
|
||||
throw new ArgumentException($"未找到ID为 '{stepId}' 的工作流步骤。");
|
||||
}
|
||||
|
||||
public bool ContainsStep(string stepId)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(stepId) && _steps.ContainsKey(stepId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有步骤的ID列表。
|
||||
/// </summary>
|
||||
public IEnumerable<string> GetAllStepIds() => _steps.Keys;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示工作流中的一个步骤,包含一个活动和可能的条件跳转。
|
||||
/// </summary>
|
||||
public class WorkflowStep
|
||||
{
|
||||
public string Id { get; } // 步骤的唯一标识符
|
||||
public IActivity Activity { get; } // 该步骤要执行的活动
|
||||
public string FlowName { get; private set; } // 所属流程名称,用于界面显示和断点定位
|
||||
public string NextStepId { get; private set; } // 默认的下一个步骤ID
|
||||
public List<JumpCondition> JumpConditions { get; } // 条件跳转列表
|
||||
|
||||
public WorkflowStep(string id, IActivity activity)
|
||||
{
|
||||
Id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
Activity = activity ?? throw new ArgumentNullException(nameof(activity));
|
||||
JumpConditions = new List<JumpCondition>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置默认的下一个步骤ID。
|
||||
/// </summary>
|
||||
public WorkflowStep WithNextStep(string nextStepId)
|
||||
{
|
||||
NextStepId = nextStepId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public WorkflowStep WithFlowName(string flowName)
|
||||
{
|
||||
FlowName = flowName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一个条件跳转。
|
||||
/// </summary>
|
||||
/// <param name="condition">跳转条件。</param>
|
||||
/// <param name="targetStepId">满足条件时要跳转到的目标步骤ID。</param>
|
||||
public WorkflowStep AddJumpCondition(Func<WorkflowContext, ActivityResult, bool> condition, string targetStepId)
|
||||
{
|
||||
JumpConditions.Add(new JumpCondition(condition, targetStepId));
|
||||
return this;
|
||||
}
|
||||
|
||||
public WorkflowStep AddDynamicJumpCondition(Func<WorkflowContext, ActivityResult, bool> condition, Func<WorkflowContext, ActivityResult, string> targetStepResolver)
|
||||
{
|
||||
JumpConditions.Add(new JumpCondition(condition, targetStepResolver));
|
||||
return this;
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnContextValue<T>(string contextKey, T expectedValue, string targetStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(contextKey)) throw new ArgumentNullException(nameof(contextKey));
|
||||
|
||||
return AddJumpCondition(
|
||||
(ctx, result) => ctx != null &&
|
||||
ctx.TryGetData<T>(contextKey, out var actualValue) &&
|
||||
EqualityComparer<T>.Default.Equals(actualValue, expectedValue),
|
||||
targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnContextValueOnSuccess<T>(string contextKey, T expectedValue, string targetStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(contextKey)) throw new ArgumentNullException(nameof(contextKey));
|
||||
|
||||
return AddJumpCondition(
|
||||
(ctx, result) => result == ActivityResult.Success &&
|
||||
ctx != null &&
|
||||
ctx.TryGetData<T>(contextKey, out var actualValue) &&
|
||||
EqualityComparer<T>.Default.Equals(actualValue, expectedValue),
|
||||
targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnRouteValue<T>(string routeKey, T expectedValue, string targetStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(routeKey)) throw new ArgumentNullException(nameof(routeKey));
|
||||
|
||||
return AddJumpCondition(
|
||||
(ctx, result) => ctx != null &&
|
||||
ctx.TryGetRoute<T>(routeKey, out var actualValue) &&
|
||||
EqualityComparer<T>.Default.Equals(actualValue, expectedValue),
|
||||
targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnRouteValueOnSuccess<T>(string routeKey, T expectedValue, string targetStepId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(routeKey)) throw new ArgumentNullException(nameof(routeKey));
|
||||
|
||||
return AddJumpCondition(
|
||||
(ctx, result) => result == ActivityResult.Success &&
|
||||
ctx != null &&
|
||||
ctx.TryGetRoute<T>(routeKey, out var actualValue) &&
|
||||
EqualityComparer<T>.Default.Equals(actualValue, expectedValue),
|
||||
targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddRouteBranchOnSuccess<T>(string routeKey, IDictionary<T, string> routeTargets)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(routeKey)) throw new ArgumentNullException(nameof(routeKey));
|
||||
if (routeTargets == null) throw new ArgumentNullException(nameof(routeTargets));
|
||||
|
||||
return AddDynamicJumpCondition(
|
||||
(ctx, result) => result == ActivityResult.Success && TryResolveRouteBranchTarget(ctx, routeKey, routeTargets, out var _),
|
||||
(ctx, result) => ResolveRouteBranchTarget(ctx, routeKey, routeTargets));
|
||||
}
|
||||
|
||||
public WorkflowStep AddRouteBranch<T>(ActivityResult expectedResult, string routeKey, IDictionary<T, string> routeTargets)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(routeKey)) throw new ArgumentNullException(nameof(routeKey));
|
||||
if (routeTargets == null) throw new ArgumentNullException(nameof(routeTargets));
|
||||
|
||||
return AddDynamicJumpCondition(
|
||||
(ctx, result) => result == expectedResult && TryResolveRouteBranchTarget(ctx, routeKey, routeTargets, out var _),
|
||||
(ctx, result) => ResolveRouteBranchTarget(ctx, routeKey, routeTargets));
|
||||
}
|
||||
|
||||
public WorkflowStep AddRouteJump(string routeContextKey = "WorkflowRoutes")
|
||||
{
|
||||
return AddDynamicJumpCondition(
|
||||
(ctx, result) => TryResolveRouteTarget(ctx, routeContextKey, out var _),
|
||||
(ctx, result) => ResolveRouteTarget(ctx, routeContextKey));
|
||||
}
|
||||
|
||||
public WorkflowStep AddRouteJumpOnSuccess(string routeContextKey = "WorkflowRoutes")
|
||||
{
|
||||
return AddDynamicJumpCondition(
|
||||
(ctx, result) => result == ActivityResult.Success && TryResolveRouteTarget(ctx, routeContextKey, out var _),
|
||||
(ctx, result) => ResolveRouteTarget(ctx, routeContextKey));
|
||||
}
|
||||
|
||||
// 可以添加更多方便的条件方法,例如:
|
||||
public WorkflowStep AddJumpOnSuccess(string targetStepId)
|
||||
{
|
||||
return AddJumpCondition((ctx, result) => result == ActivityResult.Success, targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnFailure(string targetStepId)
|
||||
{
|
||||
return AddJumpCondition((ctx, result) => result == ActivityResult.Failure, targetStepId);
|
||||
}
|
||||
|
||||
public WorkflowStep AddJumpOnCanceled(string targetStepId)
|
||||
{
|
||||
return AddJumpCondition((ctx, result) => result == ActivityResult.Canceled, targetStepId);
|
||||
}
|
||||
|
||||
private bool TryResolveRouteTarget(WorkflowContext context, string routeContextKey, out string targetStepId)
|
||||
{
|
||||
if (context != null &&
|
||||
context.TryGetData<IDictionary<string, string>>(routeContextKey, out var routes) &&
|
||||
routes != null &&
|
||||
routes.TryGetValue(Id, out targetStepId) &&
|
||||
!string.IsNullOrWhiteSpace(targetStepId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
targetStepId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ResolveRouteTarget(WorkflowContext context, string routeContextKey)
|
||||
{
|
||||
if (TryResolveRouteTarget(context, routeContextKey, out var targetStepId))
|
||||
{
|
||||
return targetStepId;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"步骤 '{Id}' 未找到有效的路由目标。上下文键: '{routeContextKey}'。");
|
||||
}
|
||||
|
||||
private bool TryResolveRouteBranchTarget<T>(WorkflowContext context, string routeKey, IDictionary<T, string> routeTargets, out string targetStepId)
|
||||
{
|
||||
if (context != null &&
|
||||
context.TryGetRoute<T>(routeKey, out var routeValue) &&
|
||||
routeTargets != null &&
|
||||
routeTargets.TryGetValue(routeValue, out targetStepId) &&
|
||||
!string.IsNullOrWhiteSpace(targetStepId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
targetStepId = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string ResolveRouteBranchTarget<T>(WorkflowContext context, string routeKey, IDictionary<T, string> routeTargets)
|
||||
{
|
||||
if (TryResolveRouteBranchTarget(context, routeKey, routeTargets, out var targetStepId))
|
||||
{
|
||||
return targetStepId;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"步骤 '{Id}' 未找到匹配的路由分支。路由键: '{routeKey}'。");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 定义一个条件跳转规则。
|
||||
/// </summary>
|
||||
public class JumpCondition
|
||||
{
|
||||
public Func<WorkflowContext, ActivityResult, bool> Condition { get; } // 判断是否跳转的条件
|
||||
public string TargetStepId { get; } // 满足条件时跳转到的目标步骤ID
|
||||
private readonly Func<WorkflowContext, ActivityResult, string> _targetStepResolver;
|
||||
|
||||
public JumpCondition(Func<WorkflowContext, ActivityResult, bool> condition, string targetStepId)
|
||||
: this(condition, (ctx, result) => targetStepId)
|
||||
{
|
||||
TargetStepId = targetStepId ?? throw new ArgumentNullException(nameof(targetStepId));
|
||||
}
|
||||
|
||||
public JumpCondition(Func<WorkflowContext, ActivityResult, bool> condition, Func<WorkflowContext, ActivityResult, string> targetStepResolver)
|
||||
{
|
||||
Condition = condition ?? throw new ArgumentNullException(nameof(condition));
|
||||
_targetStepResolver = targetStepResolver ?? throw new ArgumentNullException(nameof(targetStepResolver));
|
||||
}
|
||||
|
||||
public string ResolveTargetStepId(WorkflowContext context, ActivityResult result)
|
||||
{
|
||||
var targetStepId = _targetStepResolver(context, result);
|
||||
if (string.IsNullOrWhiteSpace(targetStepId))
|
||||
{
|
||||
throw new InvalidOperationException("跳转目标步骤ID不能为空。");
|
||||
}
|
||||
|
||||
return targetStepId;
|
||||
}
|
||||
}
|
||||
}
|
||||
529
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/WorkflowEngine.cs
Normal file
529
MX-PD-盘古 - new/PanGu.DieBonderApp/Mw.WorkFlow/WorkflowEngine.cs
Normal file
@@ -0,0 +1,529 @@
|
||||
using MW.WorkFlow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 负责执行工作流活动,并支持暂停、恢复、停止、重启和跳转,基于工作流定义和取消功能。
|
||||
/// </summary>
|
||||
public class WorkflowEngine : IDisposable
|
||||
{
|
||||
private const string WorkflowNameContextKey = "WorkflowName";
|
||||
private const string ParentActivityNameContextKey = "ParentActivityName";
|
||||
private const string CurrentStepIdContextKey = "CurrentStepId";
|
||||
private const string NextStepIdContextKey = "NextStepId";
|
||||
private const string WorkflowRuntimeTrackerContextKey = "WorkflowRuntimeTracker";
|
||||
private const string WorkflowFailureMessageContextKey = "WorkflowFailureMessage";
|
||||
private const string WorkflowFailureMessageKeyContextKey = "WorkflowFailureMessageKey";
|
||||
private const string WorkflowFailureMessageArgumentsContextKey = "WorkflowFailureMessageArguments";
|
||||
|
||||
private readonly WorkflowDefinition _workflowDefinition;
|
||||
private readonly WorkflowContext _context;
|
||||
private string _currentStepId;
|
||||
private volatile bool _isPausedRequested; // 外部请求暂停的标志
|
||||
private TaskCompletionSource<bool> _currentPauseSignal; // 当前的暂停信号
|
||||
private CancellationTokenSource _workflowCancellationTokenSource;
|
||||
private Task _workflowExecutionTask;
|
||||
private bool disposedValue;
|
||||
private readonly object _stateLock = new object(); // 用于保护共享状态的锁
|
||||
|
||||
// 当前工作流的状态
|
||||
public WorkflowState CurrentState { get; private set; } = WorkflowState.Created;
|
||||
public bool IsWorkflowPaused => _isPausedRequested;
|
||||
public bool IsWorkflowFinished => _currentStepId == null;
|
||||
public bool IsWorkflowRunning => _workflowExecutionTask != null && !_workflowExecutionTask.IsCompleted;
|
||||
public string CurrentStepId => _currentStepId;
|
||||
public Exception LastException { get; private set; }
|
||||
|
||||
public WorkflowEngine(WorkflowDefinition workflowDefinition, WorkflowContext context)
|
||||
{
|
||||
_workflowDefinition = workflowDefinition ?? throw new ArgumentNullException(nameof(workflowDefinition));
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
_currentStepId = workflowDefinition.InitialStepId;
|
||||
_isPausedRequested = false;
|
||||
|
||||
_currentPauseSignal = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
_currentPauseSignal.SetResult(true); // 初始时已经完成
|
||||
}
|
||||
|
||||
// 启动工作流的执行,可选参数允许指定起始步骤
|
||||
public void Start(string startStepId = null)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (IsWorkflowRunning)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已经在运行中,无法重复启动。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 核心修改逻辑:处理起始步骤
|
||||
if (!string.IsNullOrEmpty(startStepId))
|
||||
{
|
||||
// 验证步骤是否存在
|
||||
if (!_workflowDefinition.ContainsStep(startStepId))
|
||||
{
|
||||
throw new ArgumentException($"指定的起始步骤ID '{startStepId}' 不存在于工作流定义中。", nameof(startStepId));
|
||||
}
|
||||
_currentStepId = startStepId;
|
||||
}
|
||||
else if (_currentStepId == null)
|
||||
{
|
||||
// 如果未指定且当前没有步骤(例如上次运行已结束),重置为初始步骤
|
||||
_currentStepId = _workflowDefinition.InitialStepId;
|
||||
}
|
||||
|
||||
LastException = null;
|
||||
UpdateWorkflowState(WorkflowState.Running);
|
||||
_isPausedRequested = false;
|
||||
_workflowCancellationTokenSource?.Dispose();
|
||||
_workflowCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
// 确保 _currentPauseSignal 处于已完成状态,以便工作流立即开始
|
||||
if (!_currentPauseSignal.Task.IsCompleted)
|
||||
{
|
||||
_currentPauseSignal.TrySetResult(true);
|
||||
}
|
||||
|
||||
// 使用 Task.Run(ExecuteWorkflowInternalAsync) 避免嵌套 Task<Task>
|
||||
_workflowExecutionTask = Task.Run(ExecuteWorkflowInternalAsync);
|
||||
// 注册仅在发生未处理异常时的续体以记录异常,避免未观察异常
|
||||
_workflowExecutionTask.ContinueWith(t =>
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 工作流任务发生未处理异常: {t.Exception?.Flatten().InnerException}");
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
Debug.WriteLine($"[WorkflowEngine] 工作流已启动,从步骤 '{_currentStepId}' 开始。");
|
||||
}
|
||||
}
|
||||
|
||||
// 异步执行工作流中的活动
|
||||
private async Task ExecuteWorkflowInternalAsync()
|
||||
{
|
||||
Debug.WriteLine("\n[WorkflowEngine] 工作流执行开始。");
|
||||
CancellationToken cancellationToken = _workflowCancellationTokenSource.Token;
|
||||
|
||||
|
||||
Func<Task> pauseCheck = async () =>
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (_isPausedRequested)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已暂停,等待恢复...");
|
||||
var pauseTask = _currentPauseSignal.Task;
|
||||
var cancelTask = Task.Delay(Timeout.Infinite, cancellationToken);
|
||||
var finished = await Task.WhenAny(pauseTask, cancelTask).ConfigureAwait(false);
|
||||
if (finished == cancelTask)
|
||||
{
|
||||
// 如果取消任务先完成,则抛出取消
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
// 等待 pauseTask 以传播其可能的异常/取消
|
||||
await pauseTask.ConfigureAwait(false);
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已恢复。");
|
||||
}
|
||||
};
|
||||
ActivityControl activityControl = new ActivityControl(cancellationToken, pauseCheck);
|
||||
WorkflowStep currentStep = null;
|
||||
try
|
||||
{
|
||||
while (_currentStepId != null)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
await pauseCheck().ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
currentStep = _workflowDefinition.GetStep(_currentStepId);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 错误:无法找到步骤ID '{_currentStepId}'。工作流终止。原因: {ex.Message}");
|
||||
HandleWorkflowFault(ex, currentStep);
|
||||
_currentStepId = null;
|
||||
break;
|
||||
}
|
||||
|
||||
IActivity currentActivity = currentStep.Activity;
|
||||
_context.SetData(WorkflowFailureMessageContextKey, string.Empty);
|
||||
_context.SetData<object>(WorkflowFailureMessageKeyContextKey, null);
|
||||
_context.SetData<object[]>(WorkflowFailureMessageArgumentsContextKey, null);
|
||||
UpdateExecutionContext(currentStep);
|
||||
TrackExecutionPointer(currentStep, currentStep.Id);
|
||||
Debug.WriteLine($"\n[WorkflowEngine] 执行步骤: {currentStep.Id}, 活动: {currentActivity.Name}");
|
||||
|
||||
ActivityResult result = ActivityResult.Failure;
|
||||
try
|
||||
{
|
||||
result = await currentActivity.ExecuteAsync(_context, activityControl).ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 活动 '{currentActivity.Name}' 被取消。");
|
||||
result = ActivityResult.Canceled;
|
||||
if (CurrentState != WorkflowState.Stopped)
|
||||
{
|
||||
UpdateWorkflowState(WorkflowState.Canceled);
|
||||
}
|
||||
break;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 活动 '{currentActivity.Name}' 执行时发生未处理的异常: {ex.Message}");
|
||||
result = ActivityResult.Failure;
|
||||
HandleWorkflowFault(ex, currentStep);
|
||||
break;
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[WorkflowEngine] 步骤 '{currentStep.Id}' 完成,结果: {result}");
|
||||
|
||||
// 如果状态已变为 Faulted (可能在上面的 catch 中没 break 住,或者其他逻辑导致),双重保险停止
|
||||
if (CurrentState == WorkflowState.Faulted || CurrentState == WorkflowState.Canceled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == ActivityResult.Failure)
|
||||
{
|
||||
string failureMessage = ResolveWorkflowFailureMessage(currentActivity.Name);
|
||||
Debug.WriteLine($"[WorkflowEngine] {failureMessage}");
|
||||
HandleWorkflowFailure(failureMessage, currentStep);
|
||||
break;
|
||||
}
|
||||
|
||||
string nextStepCandidateId = null;
|
||||
|
||||
foreach (var condition in currentStep.JumpConditions)
|
||||
{
|
||||
if (condition.Condition(_context, result))
|
||||
{
|
||||
nextStepCandidateId = condition.ResolveTargetStepId(_context, result);
|
||||
Debug.WriteLine($"[WorkflowEngine] 条件跳转满足,跳转到步骤 '{nextStepCandidateId}'。");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextStepCandidateId == null && !string.IsNullOrEmpty(currentStep.NextStepId))
|
||||
{
|
||||
nextStepCandidateId = currentStep.NextStepId;
|
||||
Debug.WriteLine($"[WorkflowEngine] 默认跳转,跳转到步骤 '{nextStepCandidateId}'。");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(nextStepCandidateId) && !_workflowDefinition.ContainsStep(nextStepCandidateId))
|
||||
{
|
||||
throw new InvalidOperationException($"步骤 '{currentStep.Id}' 解析出的目标步骤 '{nextStepCandidateId}' 不存在于工作流定义中。");
|
||||
}
|
||||
|
||||
TrackExecutionPointer(currentStep, nextStepCandidateId);
|
||||
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (_currentStepId == currentStep.Id)
|
||||
{
|
||||
_currentStepId = nextStepCandidateId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 外部已修改当前步骤,跳过内部跳转逻辑。");
|
||||
}
|
||||
}
|
||||
|
||||
if (_currentStepId == null)
|
||||
{
|
||||
UpdateWorkflowState(WorkflowState.Completed);
|
||||
Debug.WriteLine($"[WorkflowEngine] 工作流已完成。");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流执行被取消。");
|
||||
if (CurrentState != WorkflowState.Stopped)
|
||||
{
|
||||
UpdateWorkflowState(WorkflowState.Canceled);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 工作流执行过程中发生未处理的异常: {ex.Message}");
|
||||
HandleWorkflowFault(ex, currentStep);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流执行结束。");
|
||||
lock (_stateLock)
|
||||
{
|
||||
_workflowExecutionTask = null;
|
||||
_currentStepId = null;
|
||||
_workflowCancellationTokenSource?.Dispose();
|
||||
_workflowCancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新工作流状态,并记录日志
|
||||
private void UpdateWorkflowState(WorkflowState newState)
|
||||
{
|
||||
if (CurrentState != newState)
|
||||
{
|
||||
CurrentState = newState;
|
||||
Debug.WriteLine($"[WorkflowEngine] 工作流状态已更改: {CurrentState}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleWorkflowFault(Exception ex, WorkflowStep currentStep)
|
||||
{
|
||||
LastException = ex;
|
||||
UpdateWorkflowState(WorkflowState.Faulted);
|
||||
|
||||
if (_context.TryGetData<IWorkflowRuntimeTracker>(WorkflowRuntimeTrackerContextKey, out var tracker) && tracker != null)
|
||||
{
|
||||
_context.TryGetData<string>(WorkflowNameContextKey, out var workflowName);
|
||||
_context.TryGetData<string>(ParentActivityNameContextKey, out var flowNameFromContext);
|
||||
_context.TryGetData<string>(CurrentStepIdContextKey, out var currentStepIdFromContext);
|
||||
|
||||
tracker.ReportWorkflowFault(new WorkflowFaultInfo
|
||||
{
|
||||
WorkflowName = workflowName,
|
||||
FlowName = currentStep?.FlowName ?? flowNameFromContext,
|
||||
ActivityName = currentStep?.Activity?.Name,
|
||||
CurrentStepId = currentStep?.Id ?? currentStepIdFromContext ?? _currentStepId,
|
||||
ErrorMessage = ex?.Message,
|
||||
OccurredAt = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleWorkflowFailure(string errorMessage, WorkflowStep currentStep)
|
||||
{
|
||||
LastException = new InvalidOperationException(errorMessage);
|
||||
UpdateWorkflowState(WorkflowState.Faulted);
|
||||
|
||||
if (_context.TryGetData<IWorkflowRuntimeTracker>(WorkflowRuntimeTrackerContextKey, out var tracker) && tracker != null)
|
||||
{
|
||||
_context.TryGetData<string>(WorkflowNameContextKey, out var workflowName);
|
||||
_context.TryGetData<string>(ParentActivityNameContextKey, out var flowNameFromContext);
|
||||
_context.TryGetData<string>(CurrentStepIdContextKey, out var currentStepIdFromContext);
|
||||
|
||||
tracker.ReportWorkflowFault(new WorkflowFaultInfo
|
||||
{
|
||||
WorkflowName = workflowName,
|
||||
FlowName = currentStep?.FlowName ?? flowNameFromContext,
|
||||
ActivityName = currentStep?.Activity?.Name,
|
||||
CurrentStepId = currentStep?.Id ?? currentStepIdFromContext ?? _currentStepId,
|
||||
ErrorMessage = errorMessage,
|
||||
OccurredAt = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveWorkflowFailureMessage(string activityName)
|
||||
{
|
||||
string failureMessage;
|
||||
if (_context.TryGetData<string>(WorkflowFailureMessageContextKey, out failureMessage) &&
|
||||
!string.IsNullOrWhiteSpace(failureMessage))
|
||||
{
|
||||
return failureMessage;
|
||||
}
|
||||
|
||||
return $"活动 '{activityName}' 返回 Failure,工作流按失败结束。";
|
||||
}
|
||||
|
||||
private void UpdateExecutionContext(WorkflowStep currentStep)
|
||||
{
|
||||
_context.SetData(CurrentStepIdContextKey, currentStep.Id);
|
||||
_context.SetData(NextStepIdContextKey, currentStep.Id);
|
||||
_context.SetData(ParentActivityNameContextKey, currentStep.FlowName ?? string.Empty);
|
||||
}
|
||||
|
||||
private void TrackExecutionPointer(WorkflowStep currentStep, string nextStepId)
|
||||
{
|
||||
_context.SetData(NextStepIdContextKey, nextStepId);
|
||||
|
||||
if (_context.TryGetData<IWorkflowRuntimeTracker>(WorkflowRuntimeTrackerContextKey, out var tracker) && tracker != null)
|
||||
{
|
||||
_context.TryGetData<string>(WorkflowNameContextKey, out var workflowName);
|
||||
tracker.UpdateExecutionPointer(new WorkflowExecutionPointer
|
||||
{
|
||||
WorkflowName = workflowName,
|
||||
FlowName = currentStep?.FlowName,
|
||||
ActivityName = currentStep?.Activity?.Name,
|
||||
CurrentStepId = currentStep?.Id,
|
||||
NextStepId = nextStepId,
|
||||
UpdatedAt = DateTime.Now
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停工作流
|
||||
public void Pause()
|
||||
{
|
||||
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (!IsWorkflowRunning)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流未运行,无法暂停。");
|
||||
return;
|
||||
}
|
||||
if (_isPausedRequested)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已处于暂停状态。");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateWorkflowState(WorkflowState.Paused);
|
||||
_isPausedRequested = true;
|
||||
if (_currentPauseSignal.Task.IsCompleted)
|
||||
{
|
||||
_currentPauseSignal = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已请求暂停。");
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复工作流
|
||||
public void Resume()
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (_isPausedRequested)
|
||||
{
|
||||
_currentPauseSignal.TrySetResult(true);
|
||||
_isPausedRequested = false;
|
||||
UpdateWorkflowState(WorkflowState.Running);
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已恢复。");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流未暂停,无需恢复。");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 停止工作流
|
||||
public async Task StopAsync()
|
||||
{
|
||||
Task taskToAwait = null;
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (!IsWorkflowRunning)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流未运行,无法停止。");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_workflowCancellationTokenSource?.IsCancellationRequested == true)
|
||||
{
|
||||
taskToAwait = _workflowExecutionTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 已请求停止工作流。");
|
||||
UpdateWorkflowState(WorkflowState.Stopped);
|
||||
_workflowCancellationTokenSource?.Cancel();
|
||||
if (_isPausedRequested)
|
||||
{
|
||||
Resume();
|
||||
}
|
||||
taskToAwait = _workflowExecutionTask;
|
||||
}
|
||||
}
|
||||
|
||||
if (taskToAwait != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await taskToAwait.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流任务被取消。");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"[WorkflowEngine] 停止工作流时发生异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
lock (_stateLock)
|
||||
{
|
||||
_workflowExecutionTask = null;
|
||||
_currentStepId = null;
|
||||
_workflowCancellationTokenSource?.Dispose();
|
||||
_workflowCancellationTokenSource = null;
|
||||
_isPausedRequested = false;
|
||||
Debug.WriteLine("[WorkflowEngine] 工作流已停止。");
|
||||
}
|
||||
}
|
||||
|
||||
// 重启工作流
|
||||
public async Task RestartAsync(string startFromStepId = null, bool clearContext = true)
|
||||
{
|
||||
await StopAsync().ConfigureAwait(false);
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (clearContext)
|
||||
{
|
||||
_context.Clear();
|
||||
}
|
||||
|
||||
_currentStepId = startFromStepId ?? _workflowDefinition.InitialStepId;
|
||||
}
|
||||
Start();
|
||||
}
|
||||
|
||||
// 跳转到指定步骤
|
||||
public void JumpTo(string stepId)
|
||||
{
|
||||
lock (_stateLock)
|
||||
{
|
||||
if (!_workflowDefinition.GetAllStepIds().Contains(stepId))
|
||||
{
|
||||
throw new ArgumentException($"步骤ID '{stepId}' 不存在。", nameof(stepId));
|
||||
}
|
||||
|
||||
_currentStepId = stepId;
|
||||
var step = _workflowDefinition.GetStep(stepId);
|
||||
UpdateExecutionContext(step);
|
||||
TrackExecutionPointer(step, stepId);
|
||||
Debug.WriteLine($"[WorkflowEngine] 跳转到步骤 '{stepId}'。");
|
||||
|
||||
if (IsWorkflowPaused)
|
||||
{
|
||||
Resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 将异步 Dispose 改为同步;在 .NET Framework 中不要在 Dispose 中使用 async void/Task 未等待
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
if (IsWorkflowRunning)
|
||||
{
|
||||
// 同步等待 StopAsync 完成以确保资源清理
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MW.WorkFlow
|
||||
{
|
||||
public enum WorkflowState
|
||||
{
|
||||
Created, // 工作流刚创建
|
||||
Running, // 工作流正在运行
|
||||
Paused, // 工作流已暂停
|
||||
Stopped, // 工作流已停止
|
||||
Completed, // 工作流已完成
|
||||
Canceled, // 工作流已取消
|
||||
Faulted // 工作流执行时发生异常
|
||||
}
|
||||
|
||||
public class WorkflowExecutionPointer
|
||||
{
|
||||
public string WorkflowName { get; set; }
|
||||
public string FlowName { get; set; }
|
||||
public string ActivityName { get; set; }
|
||||
public string CurrentStepId { get; set; }
|
||||
public string NextStepId { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class WorkflowFaultInfo
|
||||
{
|
||||
public string WorkflowName { get; set; }
|
||||
public string FlowName { get; set; }
|
||||
public string ActivityName { get; set; }
|
||||
public string CurrentStepId { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
public DateTime OccurredAt { get; set; }
|
||||
}
|
||||
|
||||
public interface IWorkflowRuntimeTracker
|
||||
{
|
||||
void UpdateExecutionPointer(WorkflowExecutionPointer executionPointer);
|
||||
void ReportWorkflowFault(WorkflowFaultInfo faultInfo);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user