public static async Task <ILock> AcquireAsync(this ILockProvider provider, IEnumerable <string> resources, TimeSpan?timeUntilExpires = null, CancellationToken cancellationToken = default) { if (resources == null) { throw new ArgumentNullException(nameof(resources)); } var resourceList = resources.Distinct().ToArray(); if (resourceList.Length == 0) { return(new EmptyLock()); } var logger = provider.GetLogger(); if (logger.IsEnabled(LogLevel.Trace)) { logger.LogTrace("Acquiring {LockCount} locks {Resource}", resourceList.Length, resourceList); } var sw = Stopwatch.StartNew(); var locks = await Task.WhenAll(resourceList.Select(r => provider.AcquireAsync(r, timeUntilExpires, cancellationToken))); sw.Stop(); // if any lock is null, release any acquired and return null (all or nothing) var acquiredLocks = locks.Where(l => l != null).ToArray(); var unacquiredResources = resourceList.Except(locks.Select(l => l?.Resource)).ToArray(); if (unacquiredResources.Length > 0) { if (logger.IsEnabled(LogLevel.Trace)) { logger.LogTrace("Unable to acquire all {LockCount} locks {Resource} releasing acquired locks", unacquiredResources.Length, unacquiredResources); } await Task.WhenAll(acquiredLocks.Select(l => l.ReleaseAsync())).AnyContext(); return(null); } if (logger.IsEnabled(LogLevel.Trace)) { logger.LogTrace("Acquired {LockCount} locks {Resource} after {Duration:g}", resourceList.Length, resourceList, sw.Elapsed); } return(new DisposableLockCollection(locks, String.Join("+", locks.Select(l => l.LockId)), sw.Elapsed, logger)); }