public static async Task WaitForAllMutexToCloseAsync() { DateTime start = DateTime.Now; lock (AsyncMutexesLock) { foreach (var mutex in AsyncMutexes) { mutex.Value.IsQuitPending = true; } } while (true) { lock (AsyncMutexesLock) { bool stillRunning = AsyncMutexes.Any(am => am.Value.IsAlive); if (!stillRunning) { return; } Logger.LogDebug($"Waiting for: {string.Join(", ", AsyncMutexes.Where(am => am.Value.IsAlive).Select(m => m.Value.ShortName))}."); } await Task.Delay(200); if (DateTime.Now - start > TimeSpan.FromSeconds(60)) { var mutexesAlive = AsyncMutexes.Where(am => am.Value.IsAlive).Select(m => m.Value.ShortName); var names = string.Join(", ", mutexesAlive); throw new TimeoutException($"{nameof(AsyncMutex)}(es) still alive after Timeout: {names}."); } } }
public AsyncMutex(string name) { // https://docs.microsoft.com/en-us/dotnet/api/system.threading.mutex?view=netframework-4.8 // On a server that is running Terminal Services, a named system mutex can have two levels of visibility. // If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions. // If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it was created. // In that case, a separate mutex with the same name can exist in each of the other terminal server sessions on the server. // If you do not specify a prefix when you create a named mutex, it takes the prefix "Local\". // Within a terminal server session, two mutexes whose names differ only by their prefixes are separate mutexes, // and both are visible to all processes in the terminal server session. // That is, the prefix names "Global\" and "Local\" describe the scope of the mutex name relative to terminal server sessions, not relative to processes. ShortName = name; FullName = $"Global\\4AA0E5A2-A94F-4B92-B962-F2BBC7A68323-{name}"; Mutex = null; MutexThread = null; // If we already have an asynclock with this fullname then just use it and do not create a new one. lock (AsyncMutexesLock) { if (AsyncMutexes.TryGetValue(FullName, out AsyncMutex asyncMutex)) { // Use the already existing lock. AsyncLock = asyncMutex.AsyncLock; } else { // Create a new lock. AsyncLock = new AsyncLock(); AsyncMutexes.Add(FullName, this); } } ChangeStatus(AsyncLockStatus.StatusReady, AsyncLockStatus.StatusUninitialized); }
public static async Task WaitForAllMutexToCloseAsync() { var start = DateTimeOffset.UtcNow; lock (AsyncMutexesLock) { foreach (var mutex in AsyncMutexes) { mutex.Value.IsQuitPending = true; } } var lastLog = DateTimeOffset.MinValue; var lastNumberOfAliveMutexes = 0; while (true) { KeyValuePair <string, AsyncMutex>[] asyncMutexes = null; lock (AsyncMutexesLock) { asyncMutexes = AsyncMutexes.ToArray(); } int numberOfAliveMutexes = asyncMutexes.Count(am => am.Value.IsAlive); if (numberOfAliveMutexes == 0) { return; } // Log every 10 seconds or status change. if ((DateTimeOffset.UtcNow - lastLog) > TimeSpan.FromSeconds(10) || lastNumberOfAliveMutexes != numberOfAliveMutexes) { Logger.LogDebug($"Waiting for: {string.Join(", ", asyncMutexes.Where(am => am.Value.IsAlive).Select(m => m.Value.ShortName))}."); lastNumberOfAliveMutexes = numberOfAliveMutexes; lastLog = DateTimeOffset.UtcNow; } await Task.Delay(100).ConfigureAwait(false); if (DateTimeOffset.UtcNow - start > TimeSpan.FromSeconds(60)) { var mutexesAlive = asyncMutexes.Where(am => am.Value.IsAlive).Select(m => m.Value.ShortName); var names = string.Join(", ", mutexesAlive); throw new TimeoutException($"{nameof(AsyncMutex)}(es) still alive after Timeout: {names}."); } } }