From 78061db9efe9d40ab8f462c410e377f991a72532 Mon Sep 17 00:00:00 2001
From: twice109 <3518499@qq.com>
Date: Sat, 3 Jan 2026 08:44:38 +0800
Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E8=BF=9B=E7=A8=8B=E5=90=AF?=
=?UTF-8?q?=E5=8A=A8=E5=99=A8=E6=8E=A5=E5=8F=A3=E7=9A=84=E8=AE=BE=E8=AE=A1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ayay.Solution.sln | 10 +-
SHH.ProcessLaunchers/DashboardLogger.cs | 72 +++
SHH.ProcessLaunchers/ILauncherLogger.cs | 53 ++
SHH.ProcessLaunchers/IProcessManager.cs | 176 +++++++
SHH.ProcessLaunchers/IResourceGuard.cs | 29 ++
SHH.ProcessLaunchers/ManagedProcess.cs | 486 ++++++++++++++++++
SHH.ProcessLaunchers/ProcessConfig.cs | 107 ++++
SHH.ProcessLaunchers/ProcessEventArgs.cs | 135 +++++
SHH.ProcessLaunchers/ProcessInfoSnapshot.cs | 42 ++
SHH.ProcessLaunchers/ProcessManager.cs | 254 +++++++++
.../SHH.ProcessLaunchers.csproj | 7 +
11 files changed, 1369 insertions(+), 2 deletions(-)
create mode 100644 SHH.ProcessLaunchers/DashboardLogger.cs
create mode 100644 SHH.ProcessLaunchers/ILauncherLogger.cs
create mode 100644 SHH.ProcessLaunchers/IProcessManager.cs
create mode 100644 SHH.ProcessLaunchers/IResourceGuard.cs
create mode 100644 SHH.ProcessLaunchers/ManagedProcess.cs
create mode 100644 SHH.ProcessLaunchers/ProcessConfig.cs
create mode 100644 SHH.ProcessLaunchers/ProcessEventArgs.cs
create mode 100644 SHH.ProcessLaunchers/ProcessInfoSnapshot.cs
create mode 100644 SHH.ProcessLaunchers/ProcessManager.cs
create mode 100644 SHH.ProcessLaunchers/SHH.ProcessLaunchers.csproj
diff --git a/Ayay.Solution.sln b/Ayay.Solution.sln
index e93051d..904f91e 100644
--- a/Ayay.Solution.sln
+++ b/Ayay.Solution.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.14.36623.8 d17.14
+# Visual Studio Version 18
+VisualStudioVersion = 18.1.11312.151 d18.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHH.CameraSdk", "SHH.CameraSdk\SHH.CameraSdk.csproj", "{21B70A94-43FC-4D17-AB83-9E4B5178397E}"
EndProject
@@ -13,6 +13,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHH.NetMQ", "SHH.NetMQ\SHH.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHH.CameraDashboard", "SHH.CameraDashboard\SHH.CameraDashboard.csproj", "{03C249D7-BCF1-404D-AD09-7AB39BA263AD}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SHH.ProcessLaunchers", "SHH.ProcessLaunchers\SHH.ProcessLaunchers.csproj", "{E12F2D41-B7BB-4303-AD01-5DCD02D7FF3C}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
{03C249D7-BCF1-404D-AD09-7AB39BA263AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03C249D7-BCF1-404D-AD09-7AB39BA263AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03C249D7-BCF1-404D-AD09-7AB39BA263AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E12F2D41-B7BB-4303-AD01-5DCD02D7FF3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E12F2D41-B7BB-4303-AD01-5DCD02D7FF3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E12F2D41-B7BB-4303-AD01-5DCD02D7FF3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E12F2D41-B7BB-4303-AD01-5DCD02D7FF3C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/SHH.ProcessLaunchers/DashboardLogger.cs b/SHH.ProcessLaunchers/DashboardLogger.cs
new file mode 100644
index 0000000..61637a3
--- /dev/null
+++ b/SHH.ProcessLaunchers/DashboardLogger.cs
@@ -0,0 +1,72 @@
+using SHH.ProcessLaunchers;
+
+namespace SHH.ProcessLauncher
+{
+ ///
+ /// 仪表盘日志适配器
+ /// 职责:实现 ILauncherLogger 接口,将启动器的底层日志转发给业务层的 LogHelper 和 UI 通知服务。
+ ///
+ public class DashboardLogger : ILauncherLogger
+ {
+ #region --- 接口实现 ---
+
+ ///
+ /// 处理普通控制台日志 (StdOut/StdErr)
+ ///
+ public void LogConsole(string processId, string message, bool isError)
+ {
+ // 1. 在控制台/调试窗口刷屏显示
+ string prefix = isError ? "[ERR]" : "[INF]";
+
+ //// 这里可以直接输出,或者通过事件抛给 UI 层去绑定 TextBox
+ //// 为了演示,我们使用 LogHelper 的 Debug 方法
+ //LogHelper.Debug($"[Console] {prefix} <{processId}>: {message}");
+ }
+
+ ///
+ /// 处理关键生命周期事件
+ ///
+ public void LogLifecycle(string processId, LogAction action, LogTrigger trigger, string reason, object payload = null)
+ {
+ // 1. 生成高可读的结构化日志文本
+ string logText = $"[{action.ToString().ToUpper()}] 触发源:{trigger} | 原因:{reason}";
+ if (payload != null) logText += $" | 数据:{payload}";
+
+ // 2. 根据触发源 (LogTrigger) 决定业务系统的响应策略
+ switch (trigger)
+ {
+ //case LogTrigger.User:
+ // // 场景:用户点击了按钮
+ // // 策略:这是预期内操作,仅记录 Info 日志,不弹窗打扰用户
+ // LogHelper.Info(processId, logText);
+ // break;
+
+ //case LogTrigger.System:
+ // // 场景:崩溃自动重启、熔断恢复等
+ // // 策略:记录 Warn 日志,运维人员需要知道系统发生了自愈行为
+ // LogHelper.Warn(processId, logText);
+ // break;
+
+ //case LogTrigger.ResourceGuard:
+ // // 场景:内存超限被杀、CPU 报警
+ // // 策略:这是严重问题,必须记录 Error 日志,并发送 UI 强提醒 (Toast/弹窗)
+ // LogHelper.Error(processId, logText);
+
+ // // 调用通知服务 (模拟)
+ // NotificationService.ShowWarning($"进程异常: {processId}", $"触发资源管控: {reason}");
+ // break;
+
+ //case LogTrigger.Scheduler:
+ // // 场景:定时重启
+ // LogHelper.Info(processId, $"[计划任务] {logText}");
+ // break;
+
+ //default:
+ // LogHelper.Info(processId, logText);
+ // break;
+ }
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ILauncherLogger.cs b/SHH.ProcessLaunchers/ILauncherLogger.cs
new file mode 100644
index 0000000..e7bb0ca
--- /dev/null
+++ b/SHH.ProcessLaunchers/ILauncherLogger.cs
@@ -0,0 +1,53 @@
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 启动器专用日志接口
+ /// 核心职责:解耦日志的生产与消费,支持结构化语义记录。
+ /// 实现类可将日志转发至 UI 控制台、本地文件或远程日志中心。
+ ///
+ public interface ILauncherLogger
+ {
+ ///
+ /// 记录普通控制台日志 (流式日志)
+ /// 用于接管子进程的 StdOut 和 StdErr
+ ///
+ /// 进程唯一标识 (ProcessConfig.Id)
+ /// 日志内容
+ /// 是否为错误流 (True=StdErr, False=StdOut)
+ void LogConsole(string processId, string message, bool isError);
+
+ ///
+ /// 记录关键生命周期事件 (结构化日志)
+ /// 用于记录启停、崩溃、熔断等关键节点,供运维分析。
+ ///
+ /// 进程唯一标识 (ProcessConfig.Id)
+ /// 动作类型 (Start/Stop/Crash...)
+ /// 触发源 (User/System...)
+ /// 操作原因或备注 (必填,用于追溯)
+ /// 附加上下文对象 (可选,如 { PID=123, ExitCode=-1 })
+ void LogLifecycle(string processId, LogAction action, LogTrigger trigger, string reason, object payload = null);
+ }
+
+ ///
+ /// 默认空日志实现 (Null Object Pattern)
+ /// 用于在未注入 Logger 时防止 NullReferenceException,保证程序健壮性。
+ ///
+ public class NullLogger : ILauncherLogger
+ {
+ ///
+ /// 空实现:忽略控制台日志
+ ///
+ public void LogConsole(string processId, string message, bool isError)
+ {
+ // Do nothing
+ }
+
+ ///
+ /// 空实现:忽略生命周期日志
+ ///
+ public void LogLifecycle(string processId, LogAction action, LogTrigger trigger, string reason, object payload = null)
+ {
+ // Do nothing
+ }
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/IProcessManager.cs b/SHH.ProcessLaunchers/IProcessManager.cs
new file mode 100644
index 0000000..2f579ce
--- /dev/null
+++ b/SHH.ProcessLaunchers/IProcessManager.cs
@@ -0,0 +1,176 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 进程管理器核心接口
+ ///
+ public interface IProcessManager
+ {
+ ///
+ /// 注册一个要管理的进程配置
+ ///
+ /// 进程配置对象
+ void Register(ProcessConfig config);
+
+ ///
+ /// 启动指定名称的进程
+ ///
+ /// 配置中定义的 Name
+ void Start(string name);
+
+ ///
+ /// 启动所有已注册的进程
+ ///
+ Task StartAllAsync();
+
+ ///
+ /// 停止指定进程 (优雅或强制)
+ ///
+ /// 进程名称
+ void Stop(string name);
+
+ ///
+ /// 停止所有进程
+ ///
+ void StopAll();
+
+ ///
+ /// 获取指定进程的资源监控复位接口
+ ///
+ /// 进程名称
+ void ResetGuard(string processName);
+
+ ///
+ /// 获取所有进程的实时状态快照
+ ///
+ List GetSnapshot();
+
+ // --- 事件定义 ---
+
+ ///
+ /// 当接收到子进程的标准输出或错误流时触发
+ ///
+ event EventHandler OnOutputReceived;
+
+ ///
+ /// 当进程生命周期状态发生变化时触发
+ ///
+ event EventHandler OnStateChanged;
+ }
+
+ public class MemoryGuard : IResourceGuard
+ {
+ public string Name => "MemoryGuard";
+
+ private readonly long _warningBytes;
+ private readonly long _criticalBytes;
+ private readonly TimeSpan _alertDuration; // 持续时间阈值 (如 3分钟)
+
+ // --- 内部状态 ---
+ private DateTime? _firstOverLimitTime; // 第一次检测到超限的时间
+ private bool _isAlertLatched = false; // 是否已经报过警 (自锁)
+
+ ///
+ /// 智能内存哨兵
+ ///
+ /// 警告阈值
+ /// 熔断阈值
+ /// 必须持续超限多少分钟才报警
+ public MemoryGuard(int warningMb, int criticalMb, int durationMinutes = 3)
+ {
+ _warningBytes = (long)warningMb * 1024 * 1024;
+ _criticalBytes = (long)criticalMb * 1024 * 1024;
+ _alertDuration = TimeSpan.FromMinutes(durationMinutes);
+ }
+
+ public GuardResult Check(Process process, out string reason)
+ {
+ reason = null;
+ try
+ {
+ process.Refresh();
+ long currentUsage = process.WorkingSet64;
+
+ // 1. 优先检查 Critical (熔断线)
+ // 逻辑:熔断涉及生死,不需要防抖,也不受“已报警”锁定的限制。
+ // 哪怕用户标记了已处置,只要内存爆了,必须重启。
+ if (currentUsage > _criticalBytes)
+ {
+ reason = $"[严重] 内存 {FormatSize(currentUsage)} > 熔断线 {FormatSize(_criticalBytes)} (立即执行管控)";
+ // 重启后,物理进程会变,下一次 Check 会是新进程,状态建议在重启时由外部重置,
+ // 或者这里不重置,依靠 ProcessManager 重启后重新创建 Guard 实例。
+ return GuardResult.Critical;
+ }
+
+ // 2. 检查 Warning (警告线) - 包含防抖和自锁逻辑
+ if (currentUsage > _warningBytes)
+ {
+ // A. 如果已经报过警 (已锁定),则不再报,保持沉默
+ if (_isAlertLatched)
+ {
+ return GuardResult.Normal;
+ }
+
+ // B. 如果是刚发现超限,记录时间
+ if (_firstOverLimitTime == null)
+ {
+ _firstOverLimitTime = DateTime.Now;
+ // 这里可以选做:记录一条 Info 日志,告诉用户"正在观察中"
+ // reason = $"内存超限 {FormatSize(currentUsage)},开始计时观察...";
+ return GuardResult.Normal; // 暂时不报 Warning
+ }
+
+ // C. 检查持续时间
+ var duration = DateTime.Now - _firstOverLimitTime.Value;
+ if (duration >= _alertDuration)
+ {
+ // 满足持续时间 -> 触发报警并锁定
+ _isAlertLatched = true;
+ _firstOverLimitTime = null; // 计时归零
+
+ reason = $"[报警] 内存 {FormatSize(currentUsage)} > 阈值 {FormatSize(_warningBytes)} 且持续超过 {_alertDuration.TotalMinutes}分钟";
+ return GuardResult.Warning; // 抛出信号,发邮件!
+ }
+ else
+ {
+ // 还没到时间
+ return GuardResult.Normal;
+ }
+ }
+ else
+ {
+ // 3. 内存正常
+ // 逻辑:如果之前在计时(比如超了1分钟),现在降下来了,则计时器清零。
+ // 但如果已经报过警 (_isAlertLatched=true),则保持锁定,不自动复位。
+ // 除非用户手动点 Reset。
+
+ if (_firstOverLimitTime != null)
+ {
+ _firstOverLimitTime = null; // 波动防抖:由于降下来了,重置观察计时
+ }
+
+ return GuardResult.Normal;
+ }
+ }
+ catch
+ {
+ return GuardResult.Normal;
+ }
+ }
+
+ ///
+ /// 用户点击“已处置”时调用
+ ///
+ public void Reset()
+ {
+ _isAlertLatched = false;
+ _firstOverLimitTime = null;
+ }
+
+ private string FormatSize(long bytes) => $"{bytes / 1024 / 1024}MB";
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/IResourceGuard.cs b/SHH.ProcessLaunchers/IResourceGuard.cs
new file mode 100644
index 0000000..ac32920
--- /dev/null
+++ b/SHH.ProcessLaunchers/IResourceGuard.cs
@@ -0,0 +1,29 @@
+using System.Diagnostics;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 资源哨兵接口 (策略模式)
+ ///
+ public interface IResourceGuard
+ {
+ ///
+ /// 哨兵名称 (如 MemoryGuard)
+ ///
+ string Name { get; }
+
+ ///
+ /// 执行健康检查
+ ///
+ /// 正在运行的进程对象
+ /// [输出] 如果异常,返回详细原因描述
+ /// 检查结果 (Normal/Warning/Critical)
+ GuardResult Check(Process process, out string reason);
+
+ ///
+ /// 人工复位/标记已处置
+ /// 用户在 UI 点击“已处置”后调用,用于清除内部的报警锁定状态 (Latch)
+ ///
+ void Reset();
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ManagedProcess.cs b/SHH.ProcessLaunchers/ManagedProcess.cs
new file mode 100644
index 0000000..54baa65
--- /dev/null
+++ b/SHH.ProcessLaunchers/ManagedProcess.cs
@@ -0,0 +1,486 @@
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SHH.ProcessLaunchers
+{
+ // =========================================================
+ // 内部核心类:单个受管进程 (封装了所有复杂逻辑)
+ // =========================================================
+ ///
+ /// 受管进程实例 (Internal Worker)
+ /// 职责:管理【单个】进程的生命周期。
+ /// 功能:包含 启动/停止/自愈/熔断/监控 的核心状态机逻辑。
+ ///
+ internal class ManagedProcess
+ {
+ #region --- 字段定义 (Fields) ---
+
+ private readonly ProcessConfig _config;
+ private readonly ProcessManager _manager;
+ private readonly ILauncherLogger _logger;
+
+ ///
+ /// 实际的操作系统进程对象
+ ///
+ private Process _process;
+
+ ///
+ /// 标记位:是否为有意的停止
+ /// True: 用户手动停止 (不自愈)
+ /// False: 运行中 (若退出则触发自愈)
+ ///
+ private bool _isIntentionalStop = true;
+
+ // --- 异步任务控制令牌 ---
+ private CancellationTokenSource _delayCts; // 用于取消重启/熔断的倒计时
+ private CancellationTokenSource _monitorCts; // 用于取消资源监控循环
+ private CancellationTokenSource _schedulerCts; // 用于取消定时重启计划
+
+ // --- 运行时统计数据 ---
+ private int _consecutiveFailures = 0; // 连续失败次数 (熔断计数器)
+ private DateTime? _lastStartTime; // 最后启动时间 (用于计算稳定运行市场)
+ private DateTime? _lastExitTime; // 最后退出时间
+ private DateTime? _nextRetryTime; // 下次自动重试的时间点
+
+ ///
+ /// 当前生命周期状态 (对外只读)
+ ///
+ public ProcessStatus Status { get; private set; } = ProcessStatus.Stopped;
+
+ ///
+ /// 公开配置信息
+ ///
+ public ProcessConfig Config => _config;
+
+ #endregion
+
+ #region --- 构造函数 ---
+
+ public ManagedProcess(ProcessConfig config, ProcessManager manager, ILauncherLogger logger)
+ {
+ _config = config;
+ _manager = manager;
+ _logger = logger;
+ }
+
+ #endregion
+
+ #region --- 外部指令 (External Commands) ---
+
+ ///
+ /// 执行启动逻辑 (入口)
+ ///
+ public void ExecuteStart(LogTrigger trigger, string reason)
+ {
+ // 如果已经在运行或启动中,则忽略
+ if (Status == ProcessStatus.Running || Status == ProcessStatus.Starting) return;
+
+ // 1. 重置所有负面状态 (用户手动介入通常意味着修复了问题)
+ _delayCts?.Cancel();
+ _isIntentionalStop = false; // 标记为"非有意停止" -> 开启守护模式
+ _consecutiveFailures = 0;
+ _nextRetryTime = null;
+
+ // 2. 记录日志
+ _logger.LogLifecycle(_config.Id, LogAction.Start, trigger, reason);
+
+ // 3. 真正启动
+ LaunchProcess();
+ }
+
+ ///
+ /// 执行停止逻辑 (入口)
+ ///
+ public void ExecuteStop(LogTrigger trigger, string reason)
+ {
+ // 1. 标记为"有意停止" -> 阻止 HandleExitLogic 触发重启
+ _isIntentionalStop = true;
+
+ // 2. 取消所有后台任务
+ _delayCts?.Cancel();
+ _monitorCts?.Cancel();
+ _schedulerCts?.Cancel();
+ _nextRetryTime = null;
+
+ // 3. 记录日志 (仅当不是已经停止时)
+ if (Status != ProcessStatus.Stopped)
+ {
+ _logger.LogLifecycle(_config.Id, LogAction.Stop, trigger, reason);
+ }
+
+ // 4. 强制杀进程
+ KillProcess();
+ UpdateStatus(ProcessStatus.Stopped);
+ }
+
+ ///
+ /// 重置资源监控锁
+ ///
+ public void ResetGuards()
+ {
+ if (_config.Guards != null)
+ {
+ foreach (var guard in _config.Guards) guard.Reset();
+ }
+ }
+
+ #endregion
+
+ #region --- 核心启动逻辑 (Core Launch Logic) ---
+
+ ///
+ /// 启动进程的原子操作
+ ///
+ private void LaunchProcess()
+ {
+ try
+ {
+ UpdateStatus(ProcessStatus.Starting);
+
+ // 1. 路径检查
+ string path = Path.GetFullPath(_config.ExePath);
+ if (!File.Exists(path))
+ {
+ _logger.LogLifecycle(_config.Id, LogAction.Error, LogTrigger.System, "可执行文件未找到", path);
+ // 关键点:文件丢失属于严重错误,直接进入退出决策逻辑(可能会触发熔断)
+ HandleExitLogic(exitCode: -1);
+ return;
+ }
+
+ // 2. 组装 ProcessStartInfo
+ var psi = new ProcessStartInfo
+ {
+ FileName = path,
+ Arguments = _config.Arguments,
+ // 如果未配置工作目录,默认使用 EXE 所在目录
+ WorkingDirectory = string.IsNullOrEmpty(_config.WorkingDirectory) ? Path.GetDirectoryName(path) : _config.WorkingDirectory,
+
+ // 窗口可见性控制
+ CreateNoWindow = !_config.Visible,
+
+ // 必须为 false 才能重定向 IO流
+ UseShellExecute = false,
+
+ // IO 重定向开关
+ RedirectStandardOutput = _config.EnableLogRedirect,
+ RedirectStandardError = _config.EnableLogRedirect
+ };
+
+ _process = new Process { StartInfo = psi, EnableRaisingEvents = true };
+
+ // 3. 绑定 IO 重定向事件 (异步读取流)
+ if (_config.EnableLogRedirect)
+ {
+ _process.OutputDataReceived += (s, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ {
+ // A. 记录到日志系统
+ _logger.LogConsole(_config.Id, e.Data, false);
+ // B. 触发对外事件 (供 UI 实时刷新)
+ _manager.DispatchOutput(_config.Id, e.Data, false);
+ }
+ };
+ _process.ErrorDataReceived += (s, e) =>
+ {
+ if (!string.IsNullOrEmpty(e.Data))
+ {
+ _logger.LogConsole(_config.Id, e.Data, true);
+ _manager.DispatchOutput(_config.Id, e.Data, true);
+ }
+ };
+ }
+
+ // 4. 绑定退出事件 (核心生命周期钩子)
+ _process.Exited += (s, e) =>
+ {
+ int code = -1;
+ try { code = _process.ExitCode; } catch { }
+ // 注意:Exited 是在后台线程触发的,转交 HandleExitLogic 处理
+ HandleExitLogic(code);
+ };
+
+ // 5. 执行操作系统启动调用
+ if (!_process.Start())
+ {
+ throw new Exception("Process.Start() 返回 false,启动失败");
+ }
+
+ // 6. 开始异步读取流 (必须在 Start 之后调用)
+ if (_config.EnableLogRedirect)
+ {
+ _process.BeginOutputReadLine();
+ _process.BeginErrorReadLine();
+ }
+
+ // 7. 更新状态
+ _lastStartTime = DateTime.Now;
+ UpdateStatus(ProcessStatus.Running);
+ _logger.LogLifecycle(_config.Id, LogAction.Start, LogTrigger.System, "进程启动成功", new { PID = _process.Id });
+
+ // 8. 启动后挂载:资源监控循环
+ StartMonitoring();
+
+ // 9. 启动后挂载:计划任务 (如果有配置)
+ if (_config.AutoRestartIntervalMinutes > 0)
+ {
+ ScheduleScheduledRestart(_config.AutoRestartIntervalMinutes * 60 * 1000);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogLifecycle(_config.Id, LogAction.Error, LogTrigger.System, $"启动过程异常: {ex.Message}");
+ HandleExitLogic(-1);
+ }
+ }
+
+ #endregion
+
+ #region --- 守护与监控逻辑 (Guard & Monitor) ---
+
+ ///
+ /// 启动资源监控后台任务
+ ///
+ private void StartMonitoring()
+ {
+ // 1. 取消旧任务
+ _monitorCts?.Cancel();
+ _monitorCts = new CancellationTokenSource();
+
+ // 如果没有配置哨兵,直接返回
+ if (_config.Guards == null || _config.Guards.Count == 0) return;
+
+ var token = _monitorCts.Token;
+
+ // 2. 启动长运行 Task
+ Task.Run(async () =>
+ {
+ while (!token.IsCancellationRequested)
+ {
+ try
+ {
+ // 默认轮询间隔 3 秒
+ await Task.Delay(3000, token);
+
+ // 每次检查前确认进程还活着
+ if (_process == null || _process.HasExited) break;
+
+ // 遍历所有哨兵
+ foreach (var guard in _config.Guards)
+ {
+ var result = guard.Check(_process, out string reason);
+
+ if (result == GuardResult.Warning)
+ {
+ // 警告级别:仅记录日志 (供客户端发邮件),不干涉进程
+ _logger.LogLifecycle(_config.Id, LogAction.ResourceCheck, LogTrigger.ResourceGuard, $"[警告] {reason}");
+ }
+ else if (result == GuardResult.Critical)
+ {
+ // 严重级别:记录日志并执行重启
+ _logger.LogLifecycle(_config.Id, LogAction.Restart, LogTrigger.ResourceGuard, $"[严重] {reason} -> 执行管控重启");
+
+ // 杀掉进程 -> 触发 Exited -> 触发 HandleExitLogic -> 自动重启
+ KillProcess();
+ return; // 退出监控循环
+ }
+ }
+ }
+ catch (TaskCanceledException) { break; } // 正常取消
+ catch (Exception ex)
+ {
+ _logger.LogConsole(_config.Id, $"监控线程异常: {ex.Message}", true);
+ }
+ }
+ }, token);
+ }
+
+ ///
+ /// 安排定时重启任务
+ ///
+ private void ScheduleScheduledRestart(int delayMs)
+ {
+ _schedulerCts?.Cancel();
+ _schedulerCts = new CancellationTokenSource();
+
+ Task.Delay(delayMs, _schedulerCts.Token).ContinueWith(t =>
+ {
+ // 只有当没被取消,且进程还在运行时,才执行重启
+ if (!t.IsCanceled && Status == ProcessStatus.Running)
+ {
+ _logger.LogLifecycle(_config.Id, LogAction.Restart, LogTrigger.Scheduler, "执行计划性重启 (AutoRestart)");
+ KillProcess(); // 触发自动重启
+ }
+ });
+ }
+
+ #endregion
+
+ #region --- 决策大脑 (Decision Logic) ---
+
+ ///
+ /// 进程退出后的核心决策逻辑 (自愈 + 熔断)
+ ///
+ /// 进程退出码
+ private void HandleExitLogic(int exitCode)
+ {
+ // 1. 清理伴生任务
+ _monitorCts?.Cancel();
+ _schedulerCts?.Cancel();
+
+ // 2. 意图判断:如果是用户手动停的,或者是计划重启中的 Kill,
+ // 这里需要判断 _isIntentionalStop。
+ // 注意:如果是用户 Stop,_isIntentionalStop 为 true,直接返回,不重启。
+ // 如果是 ResourceGuard 或 Scheduler 调用的 KillProcess,_isIntentionalStop 仍为 false,会走下面的重启逻辑。
+ if (_isIntentionalStop) return;
+
+ _lastExitTime = DateTime.Now;
+ _logger.LogLifecycle(_config.Id, LogAction.Crash, LogTrigger.System, "侦测到进程退出", new { ExitCode = exitCode });
+
+ // 3. 稳定性判定 (Stabilization Check)
+ // 逻辑:如果进程活过了阈值(如60秒),说明这次退出可能是偶发意外,不是启动即崩。
+ // 此时应重置失败计数,给予它"重新做人"的机会。
+ double runDurationMs = _lastStartTime.HasValue ? (DateTime.Now - _lastStartTime.Value).TotalMilliseconds : 0;
+
+ if (runDurationMs > _config.StabilityThresholdMs)
+ {
+ if (_consecutiveFailures > 0)
+ _logger.LogConsole(_config.Id, $"运行稳定({runDurationMs / 1000:F0}s),重置失败计数", false);
+ _consecutiveFailures = 0;
+ }
+ else
+ {
+ _consecutiveFailures++;
+ }
+
+ // 4. 熔断判定 (Circuit Breaker)
+ // 如果连续失败次数超过阈值,不再立即重启,而是进入长冷却。
+ if (_consecutiveFailures >= _config.MaxConsecutiveFailures)
+ {
+ EnterCoolingDown();
+ }
+ else
+ {
+ EnterShortRetry();
+ }
+ }
+
+ ///
+ /// 进入短时重试流程
+ ///
+ private void EnterShortRetry()
+ {
+ int delay = _config.RestartDelayMs;
+ UpdateStatus(ProcessStatus.PendingRestart);
+ _nextRetryTime = DateTime.Now.AddMilliseconds(delay);
+
+ _logger.LogLifecycle(_config.Id, LogAction.Restart, LogTrigger.System,
+ $"准备自动重启 ({_consecutiveFailures}/{_config.MaxConsecutiveFailures})", new { DelayMs = delay });
+
+ // 异步等待后执行
+ WaitAndExec(delay, () => LaunchProcess());
+ }
+
+ ///
+ /// 进入熔断冷却流程
+ ///
+ private void EnterCoolingDown()
+ {
+ int delay = _config.CircuitBreakerDelayMs;
+ UpdateStatus(ProcessStatus.CoolingDown);
+ _nextRetryTime = DateTime.Now.AddMilliseconds(delay);
+
+ _logger.LogLifecycle(_config.Id, LogAction.CircuitBreak, LogTrigger.System,
+ "触发熔断保护", new { Minutes = delay / 1000 / 60 });
+
+ // 冷却结束后,尝试恢复
+ WaitAndExec(delay, () =>
+ {
+ _logger.LogLifecycle(_config.Id, LogAction.Restart, LogTrigger.System, "熔断冷却结束,尝试恢复");
+ LaunchProcess();
+ });
+ }
+
+ ///
+ /// 通用延时执行辅助方法
+ ///
+ private void WaitAndExec(int delayMs, Action action)
+ {
+ _delayCts = new CancellationTokenSource();
+ Task.Delay(delayMs, _delayCts.Token).ContinueWith(t =>
+ {
+ // 只有未被取消才执行
+ if (!t.IsCanceled) action();
+ }, TaskScheduler.Default);
+ }
+
+ #endregion
+
+ #region --- 工具方法 (Helpers) ---
+
+ ///
+ /// 强制杀死进程 (Kill -9)
+ ///
+ private void KillProcess()
+ {
+ if (_process != null && !_process.HasExited)
+ {
+ try
+ {
+ // .NET Core 3.0+ 支持 Kill 整个进程树 (包含子进程)
+ _process.Kill();
+ _process.WaitForExit(500); // 稍微等待资源释放
+ }
+ catch { /* 忽略权限不足或竞态条件下的异常 */ }
+ }
+ }
+
+ ///
+ /// 更新状态并通知 Manager 分发事件
+ ///
+ private void UpdateStatus(ProcessStatus status)
+ {
+ if (Status != status)
+ {
+ Status = status;
+ // 回调 Manager 触发外部事件
+ _manager.DispatchStateChange(_config.Id, status);
+ }
+ }
+
+ ///
+ /// 生成当前状态快照 DTO
+ ///
+ public ProcessInfoSnapshot GetSnapshot()
+ {
+ int? pid = null;
+ try { if (Status == ProcessStatus.Running) pid = _process?.Id; } catch { }
+
+ string msg = "";
+ // 计算倒计时文本
+ if (Status == ProcessStatus.CoolingDown && _nextRetryTime.HasValue)
+ {
+ var span = _nextRetryTime.Value - DateTime.Now;
+ msg = $"熔断中 (剩余 {span.Minutes}:{span.Seconds:D2})";
+ }
+
+ return new ProcessInfoSnapshot
+ {
+ Id = _config.Id,
+ DisplayName = _config.DisplayName,
+ Pid = pid,
+ Status = Status,
+ LastStartTime = _lastStartTime,
+ LastExitTime = _lastExitTime,
+ ConsecutiveFailures = _consecutiveFailures,
+ NextRetryTime = _nextRetryTime,
+ Message = msg
+ };
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ProcessConfig.cs b/SHH.ProcessLaunchers/ProcessConfig.cs
new file mode 100644
index 0000000..f1184f9
--- /dev/null
+++ b/SHH.ProcessLaunchers/ProcessConfig.cs
@@ -0,0 +1,107 @@
+using System.Collections.Generic;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 进程启动配置项
+ ///
+ public class ProcessConfig
+ {
+ #region --- 身份标识 (Identity) ---
+
+ //
+ /// [核心变更] 唯一标识符 (Key)
+ /// 用于管理器内部索引,不可重复。例如: "Streamer_01", "Streamer_02"
+ ///
+ public string Id { get; set; } = string.Empty;
+
+ ///
+ /// [核心变更] 通用显示名称 (Category/Type)
+ /// 描述这是一类什么程序。例如: "VideoStreamingService"
+ /// 多个实例可以拥有相同的 DisplayName。
+ ///
+ public string DisplayName { get; set; } = string.Empty;
+
+ ///
+ /// 描述备注 (可选)
+ /// 例如: "负责处理 192.168.1.10 的视频流"
+ ///
+ public string Description { get; set; } = string.Empty;
+
+ #endregion
+
+ #region --- 启动参数 (Launch Args) ---
+
+ /// 可执行文件路径 (绝对路径或相对路径)
+ public string ExePath { get; set; } = string.Empty;
+
+ /// 启动参数字符串 (例如 "--id=1 --debug")
+ public string Arguments { get; set; } = string.Empty;
+
+ /// 工作目录 (默认为 Exe 所在目录)
+ public string WorkingDirectory { get; set; } = string.Empty;
+
+ #endregion
+
+ #region --- 表现层配置 ---
+
+ ///
+ /// 是否显示程序窗口
+ /// True: 弹出控制台窗口或UI | False: 后台静默运行 (CreateNoWindow=true)
+ ///
+ public bool Visible { get; set; } = false;
+
+ ///
+ /// 是否接管标准输出/错误流 (RedirectStandardOutput)
+ /// True: 启动器将捕获 Console.WriteLine 内容并通过日志接口转发。
+ /// 注意: 如果 Visible=true,建议设为 false,否则控制台窗口将是黑屏。
+ ///
+ public bool EnableLogRedirect { get; set; } = true;
+
+ #endregion
+
+ #region --- 守护策略配置 ---
+
+ /// 意外退出后的常规重启延迟 (毫秒),默认 3000ms
+ public int RestartDelayMs { get; set; } = 3000;
+
+ /// 连续失败阈值 (达到此次数后触发熔断),默认 3 次
+ public int MaxConsecutiveFailures { get; set; } = 3;
+
+ /// 熔断冷却时长 (毫秒),默认 30分钟 (1800000ms)
+ public int CircuitBreakerDelayMs { get; set; } = 30 * 60 * 1000;
+
+ ///
+ /// 稳定运行判定阈值 (毫秒)
+ /// 如果进程存活时间超过此值,则视为启动成功,重置失败计数器。
+ ///
+ public int StabilityThresholdMs { get; set; } = 60 * 1000;
+
+ ///
+ /// 自动重启间隔 (分钟)。0 表示不启用定时重启。
+ /// 用于防止内存碎片或长期运行的不稳定性。
+ ///
+ public int AutoRestartIntervalMinutes { get; set; } = 0;
+
+ #endregion
+
+ #region --- 排序启动 ---
+
+ ///
+ /// [新增] 启动顺序权重
+ /// 数字越小越先启动 (0, 1, 2...)
+ ///
+ public int StartupOrder { get; set; } = 0;
+
+ ///
+ /// [新增] 启动后等待时长 (毫秒)
+ /// 当前进程启动后,等待多久再启动下一个进程。用于防止瞬间 CPU 峰值或依赖等待。
+ ///
+ public int PostStartupDelayMs { get; set; } = 3000;
+
+ #endregion
+
+ /// 资源哨兵列表 (内存监控、心跳监控等)
+ public List Guards { get; set; } = new List();
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ProcessEventArgs.cs b/SHH.ProcessLaunchers/ProcessEventArgs.cs
new file mode 100644
index 0000000..bebcd58
--- /dev/null
+++ b/SHH.ProcessLaunchers/ProcessEventArgs.cs
@@ -0,0 +1,135 @@
+using System;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 进程输出事件参数 (StdOut/StdErr)
+ ///
+ public class ProcessOutputEventArgs : EventArgs
+ {
+ /// 进程唯一标识 (ID)
+ public string ProcessId { get; set; } = string.Empty;
+
+ /// 来源进程名称
+ public string ProcessName { get; set; } = string.Empty;
+
+ /// 输出内容
+ public string Content { get; set; } = string.Empty;
+
+ /// 是否为错误流 (StdErr)
+ public bool IsError { get; set; }
+ }
+
+ ///
+ /// 进程状态变更事件参数
+ ///
+ public class ProcessStateEventArgs : EventArgs
+ {
+ /// 进程唯一标识 (ID)
+ public string ProcessId { get; set; } = string.Empty;
+
+ /// 来源进程名称
+ public string ProcessName { get; set; } = string.Empty;
+
+ /// 变更后的新状态
+ public ProcessStatus State { get; set; }
+ }
+
+ ///
+ /// 进程生命周期状态枚举
+ ///
+ public enum ProcessStatus
+ {
+ /// 已停止 (初始状态或用户手动停止)
+ Stopped,
+
+ /// 启动中 (正在初始化进程对象)
+ Starting,
+
+ /// 运行中 (PID 已存在)
+ Running,
+
+ /// 等待重启 (崩溃后的短暂停留,默认3秒)
+ PendingRestart,
+
+ /// 熔断冷却中 (连续失败多次后的长时间等待,默认30分钟)
+ CoolingDown
+ }
+
+ ///
+ /// 资源哨兵检查结果枚举
+ ///
+ public enum GuardResult
+ {
+ /// 一切正常
+ Normal,
+
+ /// 警告 (有点问题,建议记录日志或发邮件,但不杀进程)
+ Warning,
+
+ /// 严重故障 (必须立即重启进程以保护系统)
+ Critical
+ }
+
+ ///
+ /// 操作归因:定义是谁/什么触发了这个动作
+ /// 用于后续分析是人为操作还是系统自愈
+ ///
+ public enum LogTrigger
+ {
+ ///
+ /// 用户手动干预 (UI点击、API调用)
+ /// 优先级:最高。通常视为预期内操作。
+ ///
+ User,
+
+ ///
+ /// 启动器自愈行为 (崩溃重启、初始化启动、熔断恢复)
+ /// 优先级:高。代表系统正在尝试维持服务。
+ ///
+ System,
+
+ ///
+ /// 资源哨兵触发 (内存/CPU超限)
+ /// 优先级:紧急。代表出现了亚健康状态或强制管控。
+ ///
+ ResourceGuard,
+
+ ///
+ /// 定时任务/计划调度
+ /// 优先级:中。代表按计划执行的任务。
+ ///
+ Scheduler
+ }
+
+ ///
+ /// 核心动作类型
+ /// 定义进程生命周期中发生了什么具体的事件
+ ///
+ public enum LogAction
+ {
+ /// 启动进程
+ Start,
+
+ /// 停止进程
+ Stop,
+
+ /// 重启进程
+ Restart,
+
+ /// 侦测到意外退出
+ Crash,
+
+ /// 标准输出流 (StdOut) - 通常是程序打印的普通日志
+ Output,
+
+ /// 标准错误流 (StdErr) - 程序打印的异常或错误
+ Error,
+
+ /// 触发熔断保护 (停止重试)
+ CircuitBreak,
+
+ /// 资源检查警告 (如内存超限报警,但不重启)
+ ResourceCheck
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ProcessInfoSnapshot.cs b/SHH.ProcessLaunchers/ProcessInfoSnapshot.cs
new file mode 100644
index 0000000..a48a25b
--- /dev/null
+++ b/SHH.ProcessLaunchers/ProcessInfoSnapshot.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 进程信息快照 (用于 UI 数据绑定)
+ ///
+ public class ProcessInfoSnapshot
+ {
+ /// 唯一标识 (例如: "Streamer_01")
+ public string Id { get; set; } = string.Empty;
+
+ //
+ /// 通用名称/类别 (例如: "视频取流服务")
+ /// 用于 UI 分组或显示图标
+ public string DisplayName { get; set; } = string.Empty;
+
+ /// 详细描述 (例如: "西门 1 号机位")
+ public string Description { get; set; } = string.Empty;
+
+ /// 操作系统进程 ID (运行中才有)
+ public int? Pid { get; set; }
+
+ /// 当前生命周期状态
+ public ProcessStatus Status { get; set; }
+
+ /// 最近一次启动时间
+ public DateTime? LastStartTime { get; set; }
+
+ /// 最近一次退出时间
+ public DateTime? LastExitTime { get; set; }
+
+ /// 当前连续失败次数 (用于熔断判定)
+ public int ConsecutiveFailures { get; set; }
+
+ /// 预计下次尝试启动的时间 (用于 UI 显示倒计时)
+ public DateTime? NextRetryTime { get; set; }
+
+ /// 附加状态信息 (如熔断倒计时文本)
+ public string Message { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/ProcessManager.cs b/SHH.ProcessLaunchers/ProcessManager.cs
new file mode 100644
index 0000000..8e12a88
--- /dev/null
+++ b/SHH.ProcessLaunchers/ProcessManager.cs
@@ -0,0 +1,254 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace SHH.ProcessLaunchers
+{
+ ///
+ /// 进程管理器 (核心实现类)
+ /// 核心职责:作为对外统一入口 (Facade),维护所有受管进程的容器。
+ /// 主要功能:负责路由外部指令(启动/停止)到具体的进程实例,并处理事件分发。
+ ///
+ public class ProcessManager : IProcessManager, IDisposable
+ {
+ #region --- 1. 字段与事件 (Fields & Events) ---
+
+ ///
+ /// 线程安全的进程容器
+ /// Key: ProcessConfig.Id (唯一标识)
+ /// Value: ManagedProcess (受管实例)
+ ///
+ private readonly ConcurrentDictionary _processes
+ = new ConcurrentDictionary();
+
+ ///
+ /// 日志服务接口 (依赖注入)
+ ///
+ private readonly ILauncherLogger _logger;
+
+ // ---------------------------------------------------------
+ // 对外暴露的事件定义
+ // ---------------------------------------------------------
+
+ ///
+ /// 对外事件:当接收到任意子进程的标准输出/错误流时触发
+ ///
+ public event EventHandler OnOutputReceived;
+
+ ///
+ /// 对外事件:当任意子进程的状态发生变更时触发
+ ///
+ public event EventHandler OnStateChanged;
+
+ #endregion
+
+ #region --- 2. 构造与析构 (Constructor & Dispose) ---
+
+ ///
+ /// 初始化进程管理器实例
+ ///
+ /// 日志实现类 (若外部未传入,则内部自动使用 NullLogger 以防止空引用异常)
+ public ProcessManager(ILauncherLogger logger = null)
+ {
+ // 规范化:使用空合并运算符确保 _logger 永不为 null
+ _logger = logger ?? new NullLogger();
+ }
+
+ ///
+ /// 销毁资源,停止所有进程并清理事件订阅
+ ///
+ public void Dispose()
+ {
+ // 1. 停止所有子进程 (触发 Kill 操作,清理进程树)
+ StopAll();
+
+ // 2. 清空内部容器引用
+ _processes.Clear();
+
+ // 3. 移除所有外部事件订阅,防止 UI 端因未解绑而导致的内存泄露
+ OnOutputReceived = null;
+ OnStateChanged = null;
+ }
+
+ #endregion
+
+ #region --- 3. 公共 API 实现 (Public Methods) ---
+
+ ///
+ /// 注册一个新的进程配置到管理器中
+ ///
+ /// 进程配置对象 (包含 Exe路径、参数、熔断策略等)
+ /// 当 Id 为空时抛出
+ /// 当 Id 已存在时抛出
+ public void Register(ProcessConfig config)
+ {
+ // 1. 基础参数校验:确保 Id 存在
+ if (string.IsNullOrWhiteSpace(config.Id))
+ throw new ArgumentException("进程配置无效:必须包含唯一的 Id");
+
+ // 2. 防重复注册校验:确保字典中没有相同的 Key
+ if (_processes.ContainsKey(config.Id))
+ throw new InvalidOperationException($"进程 Id '{config.Id}' 已存在,禁止重复注册。");
+
+ // 3. 实例化受管进程对象 (传入 this 指针是为了后续回调 DispatchXXX 方法)
+ var process = new ManagedProcess(config, this, _logger);
+
+ // 4. 加入线程安全字典
+ if (_processes.TryAdd(config.Id, process))
+ {
+ _logger.LogLifecycle(config.Id, LogAction.Output, LogTrigger.System,
+ $"进程配置已注册: {config.DisplayName}");
+ }
+ }
+
+ ///
+ /// 启动指定 ID 的进程
+ ///
+ /// 进程的唯一标识符 (ProcessConfig.Id)
+ public void Start(string id)
+ {
+ // 尝试获取指定 ID 的进程实例
+ if (_processes.TryGetValue(id, out var p))
+ {
+ // 调用内部实例的启动逻辑,操作归因标记为"User" (用户手动)
+ p.ExecuteStart(LogTrigger.User, "用户手动启动指令");
+ }
+ else
+ {
+ // 如果找不到,记录错误日志
+ _logger.LogLifecycle(id, LogAction.Error, LogTrigger.User, "启动失败:未找到指定 ID 的进程配置");
+ }
+ }
+
+ ///
+ /// [异步] 有序批量启动所有进程
+ /// 按照 StartupOrder 从小到大排序启动,并支持启动间隙延时 (PostStartupDelayMs)。
+ ///
+ /// 异步任务
+ public async Task StartAllAsync()
+ {
+ _logger.LogLifecycle("ALL", LogAction.Start, LogTrigger.User, "执行有序批量启动");
+
+ // 1. 数据准备:从字典取出所有进程,并按配置进行排序
+ // 排序规则:StartupOrder (小->大) -> Id (字母序) 以保证启动顺序的确定性
+ var sortedList = _processes.Values
+ .OrderBy(p => p.Config.StartupOrder) // 按用户指定的权重排
+ .ThenBy(p => p.Config.Id) // 权重一样时按 ID 排
+ .ToList();
+
+ // 2. 顺序执行启动循环
+ foreach (var p in sortedList)
+ {
+ // 同步调用启动指令(注意:这里不等待进程完全 Ready,只负责拉起进程)
+ p.ExecuteStart(LogTrigger.User, "有序批量启动");
+
+ // 3. 处理启动间隙延迟 (错峰启动)
+ // 作用:防止多个重型进程同时启动导致 CPU/IO 瞬间拥堵
+ int delay = p.Config.PostStartupDelayMs;
+ if (delay > 0)
+ {
+ // 异步等待指定毫秒数,释放线程控制权
+ await Task.Delay(delay);
+ }
+ }
+
+ _logger.LogLifecycle("ALL", LogAction.Start, LogTrigger.User, "有序批量启动完成");
+ }
+
+ ///
+ /// 停止指定 ID 的进程
+ ///
+ /// 进程的唯一标识符
+ public void Stop(string id)
+ {
+ if (_processes.TryGetValue(id, out var p))
+ {
+ p.ExecuteStop(LogTrigger.User, "用户手动停止指令");
+ }
+ }
+
+ ///
+ /// 批量停止所有进程 (并发执行)
+ ///
+ public void StopAll()
+ {
+ _logger.LogLifecycle("ALL", LogAction.Stop, LogTrigger.User, "执行批量停止");
+
+ // 遍历所有进程,使用 Task.Run 并发执行停止,提高效率,无需等待
+ foreach (var p in _processes.Values)
+ {
+ Task.Run(() => p.ExecuteStop(LogTrigger.User, "批量停止"));
+ }
+ }
+
+ ///
+ /// 重置/复位指定进程的资源报警状态
+ /// 当用户在 UI 上点击"已处置"后调用此方法,解除报警锁定。
+ ///
+ /// 进程的唯一标识符
+ public void ResetGuard(string id)
+ {
+ if (_processes.TryGetValue(id, out var p))
+ {
+ // 调用内部复位逻辑,清除报警锁定状态
+ p.ResetGuards();
+ _logger.LogLifecycle(id, LogAction.ResourceCheck, LogTrigger.User, "用户手动复位资源报警锁");
+ }
+ }
+
+ ///
+ /// 获取当前所有进程的实时状态快照
+ /// 用于 UI 列表的数据绑定或定时刷新。
+ ///
+ /// 进程信息快照列表
+ public List GetSnapshot()
+ {
+ // 将字典中的所有受管对象转为 DTO 快照列表
+ return _processes.Values.Select(p => p.GetSnapshot()).ToList();
+ }
+
+ #endregion
+
+ #region --- 4. 内部事件分发 (Internal Dispatchers) ---
+
+ // 说明:C# 的 event 只能在定义类内部 Invoke。
+ // 为了让内部类 ManagedProcess 也能触发 Manager 的对外事件,我们提供了这几个 internal 方法。
+ // 这些方法充当了内部类与外部事件之间的桥梁。
+
+ ///
+ /// 分发状态变更事件 (供 ManagedProcess 内部调用)
+ ///
+ /// 进程 ID
+ /// 新的状态
+ internal void DispatchStateChange(string processId, ProcessStatus newState)
+ {
+ // 线程安全地触发事件
+ OnStateChanged?.Invoke(this, new ProcessStateEventArgs
+ {
+ ProcessId = processId,
+ State = newState
+ });
+ }
+
+ ///
+ /// 分发日志输出事件 (供 ManagedProcess 内部调用)
+ ///
+ /// 进程 ID
+ /// 日志内容
+ /// 是否为错误流
+ internal void DispatchOutput(string processId, string content, bool isError)
+ {
+ // 线程安全地触发事件
+ OnOutputReceived?.Invoke(this, new ProcessOutputEventArgs
+ {
+ ProcessId = processId,
+ Content = content,
+ IsError = isError
+ });
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/SHH.ProcessLaunchers/SHH.ProcessLaunchers.csproj b/SHH.ProcessLaunchers/SHH.ProcessLaunchers.csproj
new file mode 100644
index 0000000..dbdcea4
--- /dev/null
+++ b/SHH.ProcessLaunchers/SHH.ProcessLaunchers.csproj
@@ -0,0 +1,7 @@
+
+
+
+ netstandard2.0
+
+
+