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 } }