/// <summary>
        /// Tries to unlock the given keys.
        /// </summary>
        /// <param name="keys">The keys to unlock.</param>
        /// <param name="lockValue">The value that was used to lock the keys.</param>
        /// <param name="luaScript">The lua script to unlock the keys.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <returns>How many keys were unlocked.</returns>
        public int UnlockMany(string[] keys, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            if (luaScript == null)
            {
                throw new ArgumentNullException(nameof(luaScript));
            }

            var lockKeys = new RedisKey[keys.Length];

            for (var i = 0; i < keys.Length; i++)
            {
                lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
            }
            if (extraKeys != null)
            {
                lockKeys = lockKeys.Concat(extraKeys).ToArray();
            }
            var values = new RedisValue[] { lockValue };

            if (extraValues != null)
            {
                values = values.Concat(extraValues).ToArray();
            }

            var result = (RedisValue[])_database.ScriptEvaluate(luaScript, lockKeys, values);

            return((int)result[0]);
        }
        /// <summary>
        /// Tries to unlock the given key.
        /// </summary>
        /// <param name="key">The key to unlock.</param>
        /// <param name="lockValue">The value that was used to lock the key.</param>
        /// <param name="luaScript">The lua script to unlock the key.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <returns>Whether the key was unlocked.</returns>
        public bool Unlock(string key, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            var lockKey = $"{key}{_lockKeySuffix}";

            if (string.IsNullOrEmpty(luaScript))
            {
                return(_database.LockRelease(lockKey, lockValue));
            }
            var keys = new RedisKey[] { lockKey };

            if (extraKeys != null)
            {
                keys = keys.Concat(extraKeys).ToArray();
            }
            var values = new RedisValue[] { lockValue };

            if (extraValues != null)
            {
                values = values.Concat(extraValues).ToArray();
            }

            var result = (RedisValue[])_database.ScriptEvaluate(luaScript, keys, values);

            return((bool)result[0]);
        }
        /// <summary>
        /// Tries to lock the given key.
        /// </summary>
        /// <param name="key">The key to lock.</param>
        /// <param name="luaScript">The lua script to lock the key.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
        /// <returns>The lock value used to lock the key.</returns>
        /// <exception cref="CacheException">Thrown if the lock was not acquired.</exception>
        public Task <string> LockAsync(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <string>(cancellationToken));
            }
            var lockKey = $"{key}{_lockKeySuffix}";

            string Context() => lockKey;

            return(_retryPolicy.ExecuteAsync(async() =>
            {
                var lockValue = _lockValueProvider.GetValue();
                if (!string.IsNullOrEmpty(luaScript))
                {
                    var keys = new RedisKey[] { lockKey };
                    if (extraKeys != null)
                    {
                        keys = keys.Concat(extraKeys).ToArray();
                    }
                    var values = new RedisValue[] { lockValue, (long)_lockTimeout.TotalMilliseconds };
                    if (extraValues != null)
                    {
                        values = values.Concat(extraValues).ToArray();
                    }
                    var result = (RedisValue[])await(_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false);
                    if ((bool)result[0])
                    {
                        return lockValue;
                    }
                }
                else if (await(_database.LockTakeAsync(lockKey, lockValue, _lockTimeout)).ConfigureAwait(false))
                {
                    return lockValue;
                }

                return null;                 // retry
            }, Context, cancellationToken));
        }
        /// <summary>
        /// Tries to lock the given keys.
        /// </summary>
        /// <param name="keys">The keys to lock.</param>
        /// <param name="luaScript">The lua script to lock the keys.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
        /// <returns>The lock value used to lock the keys.</returns>
        /// <exception cref="CacheException">Thrown if the lock was not acquired.</exception>
        public Task <string> LockManyAsync(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            if (luaScript == null)
            {
                throw new ArgumentNullException(nameof(luaScript));
            }
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <string>(cancellationToken));
            }
            string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}"));

            return(_retryPolicy.ExecuteAsync(async() =>
            {
                var lockKeys = new RedisKey[keys.Length];
                for (var i = 0; i < keys.Length; i++)
                {
                    lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
                }
                var lockValue = _lockValueProvider.GetValue();
                if (extraKeys != null)
                {
                    lockKeys = lockKeys.Concat(extraKeys).ToArray();
                }
                var values = new RedisValue[] { lockValue, (long)_lockTimeout.TotalMilliseconds };
                if (extraValues != null)
                {
                    values = values.Concat(extraValues).ToArray();
                }
                var result = (RedisValue[])await(_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false);
                if ((bool)result[0])
                {
                    return lockValue;
                }

                return null;                 // retry
            }, Context, cancellationToken));
        }
        /// <summary>
        /// Tries to unlock the given keys.
        /// </summary>
        /// <param name="keys">The keys to unlock.</param>
        /// <param name="lockValue">The value that was used to lock the keys.</param>
        /// <param name="luaScript">The lua script to unlock the keys.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
        /// <returns>How many keys were unlocked.</returns>
        public Task <int> UnlockManyAsync(string[] keys, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            if (luaScript == null)
            {
                throw new ArgumentNullException(nameof(luaScript));
            }
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <int>(cancellationToken));
            }
            return(InternalUnlockManyAsync());

            async Task <int> InternalUnlockManyAsync()
            {
                var lockKeys = new RedisKey[keys.Length];

                for (var i = 0; i < keys.Length; i++)
                {
                    lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
                }
                if (extraKeys != null)
                {
                    lockKeys = lockKeys.Concat(extraKeys).ToArray();
                }
                var values = new RedisValue[] { lockValue };

                if (extraValues != null)
                {
                    values = values.Concat(extraValues).ToArray();
                }
                cancellationToken.ThrowIfCancellationRequested();

                var result = (RedisValue[])await(_database.ScriptEvaluateAsync(luaScript, lockKeys, values)).ConfigureAwait(false);

                return((int)result[0]);
            }
        }
        /// <summary>
        /// Tries to unlock the given key.
        /// </summary>
        /// <param name="key">The key to unlock.</param>
        /// <param name="lockValue">The value that was used to lock the key.</param>
        /// <param name="luaScript">The lua script to unlock the key.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
        /// <returns>Whether the key was unlocked.</returns>
        public Task <bool> UnlockAsync(string key, string lockValue, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues, CancellationToken cancellationToken)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (cancellationToken.IsCancellationRequested)
            {
                return(Task.FromCanceled <bool>(cancellationToken));
            }
            return(InternalUnlockAsync());

            async Task <bool> InternalUnlockAsync()
            {
                var lockKey = $"{key}{_lockKeySuffix}";

                if (string.IsNullOrEmpty(luaScript))
                {
                    cancellationToken.ThrowIfCancellationRequested();
                    return(await(_database.LockReleaseAsync(lockKey, lockValue)).ConfigureAwait(false));
                }
                var keys = new RedisKey[] { lockKey };

                if (extraKeys != null)
                {
                    keys = keys.Concat(extraKeys).ToArray();
                }
                var values = new RedisValue[] { lockValue };

                if (extraValues != null)
                {
                    values = values.Concat(extraValues).ToArray();
                }
                cancellationToken.ThrowIfCancellationRequested();

                var result = (RedisValue[])await(_database.ScriptEvaluateAsync(luaScript, keys, values)).ConfigureAwait(false);

                return((bool)result[0]);
            }
        }
        /// <summary>
        /// Tries to lock the given key.
        /// </summary>
        /// <param name="key">The key to lock.</param>
        /// <param name="luaScript">The lua script to lock the key.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <returns>The lock value used to lock the key.</returns>
        /// <exception cref="CacheException">Thrown if the lock was not acquired.</exception>
        public string Lock(string key, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues)
        {
            if (key == null)
            {
                throw new ArgumentNullException(nameof(key));
            }
            var lockKey = $"{key}{_lockKeySuffix}";

            string Context() => lockKey;

            return(_retryPolicy.Execute(() =>
            {
                var lockValue = _lockValueProvider.GetValue();
                if (!string.IsNullOrEmpty(luaScript))
                {
                    var keys = new RedisKey[] { lockKey };
                    if (extraKeys != null)
                    {
                        keys = keys.Concat(extraKeys).ToArray();
                    }
                    var values = new RedisValue[] { lockValue, (long)_lockTimeout.TotalMilliseconds };
                    if (extraValues != null)
                    {
                        values = values.Concat(extraValues).ToArray();
                    }
                    var result = (RedisValue[])_database.ScriptEvaluate(luaScript, keys, values);
                    if ((bool)result[0])
                    {
                        return lockValue;
                    }
                }
                else if (_database.LockTake(lockKey, lockValue, _lockTimeout))
                {
                    return lockValue;
                }

                return null;                 // retry
            }, Context));
        }
        /// <summary>
        /// Tries to lock the given keys.
        /// </summary>
        /// <param name="keys">The keys to lock.</param>
        /// <param name="luaScript">The lua script to lock the keys.</param>
        /// <param name="extraKeys">The extra keys that will be provided to the <paramref name="luaScript"/></param>
        /// <param name="extraValues">The extra values that will be provided to the <paramref name="luaScript"/></param>
        /// <returns>The lock value used to lock the keys.</returns>
        /// <exception cref="CacheException">Thrown if the lock was not acquired.</exception>
        public string LockMany(string[] keys, string luaScript, RedisKey[] extraKeys, RedisValue[] extraValues)
        {
            if (keys == null)
            {
                throw new ArgumentNullException(nameof(keys));
            }
            if (luaScript == null)
            {
                throw new ArgumentNullException(nameof(luaScript));
            }
            string Context() => string.Join(",", keys.Select(o => $"{o}{_lockKeySuffix}"));

            return(_retryPolicy.Execute(() =>
            {
                var lockKeys = new RedisKey[keys.Length];
                for (var i = 0; i < keys.Length; i++)
                {
                    lockKeys[i] = $"{keys[i]}{_lockKeySuffix}";
                }
                var lockValue = _lockValueProvider.GetValue();
                if (extraKeys != null)
                {
                    lockKeys = lockKeys.Concat(extraKeys).ToArray();
                }
                var values = new RedisValue[] { lockValue, (long)_lockTimeout.TotalMilliseconds };
                if (extraValues != null)
                {
                    values = values.Concat(extraValues).ToArray();
                }
                var result = (RedisValue[])_database.ScriptEvaluate(luaScript, lockKeys, values);
                if ((bool)result[0])
                {
                    return lockValue;
                }

                return null;                 // retry
            }, Context));
        }