using System; using System.IO; using System.IO.Pipes; using System.Threading; using System.Threading.Tasks; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Threading; using Serilog; namespace YZWater.Avalonia; /// /// 单实例管理:确保程序只运行一个实例,新启动时激活已有窗口 /// internal static class SingleInstanceHelper { private const string MutexName = "Global\\YZWater3_SingleInstance"; private const string PipeName = "YZWater3_SingleInstance_Pipe"; private static Mutex? _mutex; private static CancellationTokenSource? _cts; /// /// 尝试获取单实例权限。true 表示这是第一个实例(或去重机制本身没建起来,照常启动)。 /// public static bool TryAcquire() { try { _mutex = new Mutex(true, MutexName, out bool createdNew); return createdNew; } catch (Exception ex) { // 单实例只是体验优化,不能当启动闸门。此刻 Serilog 还没初始化(在 App 构造函数里), // 异常抛出去就是"双击图标没反应、一行日志都找不到"。 // Global\ 命名空间还受账户权限影响(受限账户/服务账户未必建得起来), // 失败时放弃去重、继续启动,最多是开出两个窗口,总比开不了机强。 _mutex = null; Console.Error.WriteLine($"单实例互斥体创建失败,跳过去重继续启动: {ex.Message}"); return true; } } /// /// 通知已有实例激活窗口 /// public static void ActivateExistingInstance() { try { using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out); client.Connect(1000); // 1秒超时 using var writer = new StreamWriter(client); writer.WriteLine("ACTIVATE"); writer.Flush(); } catch (Exception ex) { Log.Warning(ex, "通知已有实例失败"); } } /// /// 启动监听,等待新实例的激活请求 /// public static void StartListening() { _cts = new CancellationTokenSource(); var token = _cts.Token; Task.Run(() => { while (!token.IsCancellationRequested) { try { using var server = new NamedPipeServerStream( PipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); // 等待连接(异步,可取消) var ar = server.BeginWaitForConnection(null, null); // 等待句柄完成,同时监听取消 var handles = new[] { ar.AsyncWaitHandle, token.WaitHandle }; var idx = WaitHandle.WaitAny(handles); if (idx == 1) // token 被取消 { break; } server.EndWaitForConnection(ar); if (!server.IsConnected) continue; // 读取命令 using var reader = new StreamReader(server); var command = reader.ReadLine(); if (command == "ACTIVATE") { // 在 UI 线程激活主窗口 Dispatcher.UIThread.Post(() => { ActivateMainWindow(); }); } // 断开后继续等待下一次 server.Disconnect(); } catch (Exception ex) { if (token.IsCancellationRequested) break; Log.Warning(ex, "单实例监听异常"); Thread.Sleep(500); // 避免异常风暴 } } }, token); } /// /// 激活主窗口(带到前台并聚焦) /// private static void ActivateMainWindow() { try { if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { var mainWindow = desktop.MainWindow; if (mainWindow == null) return; // 如果窗口最小化,恢复正常状态 if (mainWindow.WindowState == WindowState.Minimized) { mainWindow.WindowState = WindowState.Normal; } // 激活窗口(带到前台) mainWindow.Activate(); mainWindow.Focus(); // 尝试置顶再取消(某些系统需要这个技巧才能真正到前台) var oldTopmost = mainWindow.Topmost; mainWindow.Topmost = true; Task.Delay(100).ContinueWith(_ => { Dispatcher.UIThread.Post(() => { mainWindow.Topmost = oldTopmost; }); }); Log.Information("已激活主窗口"); } } catch (Exception ex) { Log.Warning(ex, "激活主窗口失败"); } } /// /// 释放单实例资源 /// public static void Release() { Serilog.Log.Information("SingleInstanceHelper 正在释放..."); _cts?.Cancel(); // 等待监听任务完成 // 注意:不能 Wait 太久,因为可能在 UI 线程调用 _cts?.Dispose(); _cts = null; try { _mutex?.ReleaseMutex(); _mutex?.Dispose(); } catch (Exception ex) { Serilog.Log.Warning(ex, "SingleInstanceHelper Mutex 释放失败"); } _mutex = null; Serilog.Log.Information("SingleInstanceHelper 已释放"); } }