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 已释放");
}
}