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(); } } }