feat: 实现单实例应用程序支持

This commit is contained in:
2026-01-17 16:34:18 +08:00
parent 74ca8e4d57
commit 7abd445039
3 changed files with 151 additions and 6 deletions

View File

@@ -572,6 +572,23 @@ namespace TimerApp
this.Activate();
}
public void ActivateFromExternal()
{
if (InvokeRequired)
{
BeginInvoke(new Action(ActivateFromExternal));
return;
}
this.Show();
this.WindowState = FormWindowState.Normal;
this.ShowInTaskbar = true;
this.BringToFront();
this.Activate();
this.TopMost = true;
this.TopMost = false;
}
private void toolStripMenuItemExit_Click(object sender, EventArgs e)
{
_monitor.Stop();

View File

@@ -7,12 +7,22 @@ static class Program
/// </summary>
[STAThread]
static void Main()
{
if (!SingleInstanceManager.TryAcquire(out var instance) || instance is null)
{
SingleInstanceManager.SignalExistingInstance();
return;
}
using (instance)
{
TaskbarIntegration.InitializeProcess();
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
TaskbarIntegration.InitializeShortcuts();
Application.Run(new MainForm());
var mainForm = new MainForm();
instance.StartServer(mainForm.ActivateFromExternal);
Application.Run(mainForm);
}
}
}

118
SingleInstanceManager.cs Normal file
View File

@@ -0,0 +1,118 @@
using System;
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
namespace TimerApp
{
internal sealed class SingleInstanceManager : IDisposable
{
private const string MutexName = "Local\\TimerApp.SingleInstance";
private const string PipeName = "TimerApp.SingleInstancePipe";
private readonly Mutex _mutex;
private readonly CancellationTokenSource _cts = new();
private SingleInstanceManager(Mutex mutex)
{
_mutex = mutex;
}
public static bool TryAcquire(out SingleInstanceManager? manager)
{
manager = null;
try
{
var mutex = new Mutex(true, MutexName, out bool createdNew);
if (!createdNew)
{
mutex.Dispose();
return false;
}
manager = new SingleInstanceManager(mutex);
return true;
}
catch
{
return true;
}
}
public static void SignalExistingInstance()
{
try
{
using var client = new NamedPipeClientStream(".", PipeName, PipeDirection.Out);
client.Connect(200);
using var writer = new StreamWriter(client) { AutoFlush = true };
writer.WriteLine("show");
}
catch
{
}
}
public void StartServer(Action onShowRequested)
{
Task.Run(() => ServerLoop(onShowRequested, _cts.Token));
}
private static async Task ServerLoop(Action onShowRequested, CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
try
{
using var server = new NamedPipeServerStream(
PipeName,
PipeDirection.In,
1,
PipeTransmissionMode.Byte,
PipeOptions.Asynchronous
);
await server.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);
using var reader = new StreamReader(server);
string? line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
if (string.Equals(line, "show", StringComparison.OrdinalIgnoreCase))
onShowRequested();
}
catch (OperationCanceledException)
{
return;
}
catch
{
await Task.Delay(150, cancellationToken).ConfigureAwait(false);
}
}
}
public void Dispose()
{
try
{
_cts.Cancel();
}
catch
{
}
try
{
_mutex.ReleaseMutex();
}
catch
{
}
_mutex.Dispose();
_cts.Dispose();
}
}
}