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