| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- 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;
- /// <summary>
- /// 单实例管理:确保程序只运行一个实例,新启动时激活已有窗口
- /// </summary>
- 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;
- /// <summary>
- /// 尝试获取单实例权限。true 表示这是第一个实例(或去重机制本身没建起来,照常启动)。
- /// </summary>
- 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;
- }
- }
- /// <summary>
- /// 通知已有实例激活窗口
- /// </summary>
- 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, "通知已有实例失败");
- }
- }
- /// <summary>
- /// 启动监听,等待新实例的激活请求
- /// </summary>
- 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);
- }
- /// <summary>
- /// 激活主窗口(带到前台并聚焦)
- /// </summary>
- 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, "激活主窗口失败");
- }
- }
- /// <summary>
- /// 释放单实例资源
- /// </summary>
- 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 已释放");
- }
- }
|