diff --git a/src/FreeRedis/RedisClient/Modules/Lock.cs b/src/FreeRedis/RedisClient/Modules/Lock.cs
index 8c9623c..805c997 100644
--- a/src/FreeRedis/RedisClient/Modules/Lock.cs
+++ b/src/FreeRedis/RedisClient/Modules/Lock.cs
@@ -5,145 +5,170 @@
namespace FreeRedis
{
- partial class RedisClient
- {
- ///
- /// 开启分布式锁,若超时返回null
- ///
- /// 锁名称
- /// 超时(秒)
- /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
- ///
- public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
- {
- name = $"RedisClientLock:{name}";
- var startTime = DateTime.Now;
- while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
- {
- var value = Guid.NewGuid().ToString();
- if (SetNx(name, value, timeoutSeconds) == true)
- {
- double refreshSeconds = (double)timeoutSeconds / 2.0;
- return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
- }
- Thread.CurrentThread.Join(3);
- }
- return null;
- }
-
- ///
- /// 开启分布式锁,若超时返回null
- ///
- /// 锁名称
- /// 独占锁过期时间
- /// 每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义
- /// 等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null
- /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
- /// CancellationToken 自动取消锁
- ///
- public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
- {
- if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
- if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;
-
- name = $"RedisClientLock:{name}";
- var startTime = DateTime.Now;
-
- // 规定时间内等待锁释放
- while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
- {
- var value = Guid.NewGuid().ToString();
- if (SetNx(name, value, timeoutSeconds) == true)
- {
- return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
- }
- if (token.IsCancellationRequested) return null;
- Thread.CurrentThread.Join(millisecondsTimeout: 3);
- }
- return null;
- }
-
- public class LockController : IDisposable
- {
-
- RedisClient _client;
- string _name;
- string _value;
- int _timeoutSeconds;
- Timer _autoDelayTimer;
- private readonly CancellationToken _token;
- internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
- {
- _client = rds;
- _name = name;
- _value = value;
- _timeoutSeconds = timeoutSeconds;
- _token = token;
- if (autoDelay)
- {
- var refreshMilli = (int)(refreshSeconds * 1000);
- var timeoutMilli = timeoutSeconds * 1000;
- _autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
- }
- }
-
- ///
- /// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
- ///
- /// 延长的毫秒数
- /// 成功/失败
- public bool Delay(int milliseconds)
- {
- var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ partial class RedisClient
+ {
+ ///
+ /// 开启分布式锁,若超时返回null
+ ///
+ /// 锁名称
+ /// 超时(秒)
+ /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
+ ///
+ public LockController Lock(string name, int timeoutSeconds, bool autoDelay = true)
+ {
+ name = $"RedisClientLock:{name}";
+ var startTime = DateTime.Now;
+ while (DateTime.Now.Subtract(startTime).TotalSeconds < timeoutSeconds)
+ {
+ var value = Guid.NewGuid().ToString();
+ if (SetNx(name, value, timeoutSeconds) == true)
+ {
+ double refreshSeconds = (double)timeoutSeconds / 2.0;
+ return new LockController(this, name, value, timeoutSeconds, refreshSeconds, autoDelay, CancellationToken.None);
+ }
+ Thread.CurrentThread.Join(3);
+ }
+ return null;
+ }
+
+ ///
+ /// 开启分布式锁,若超时返回null
+ ///
+ /// 锁名称
+ /// 独占锁过期时间
+ /// 每隔多久自动延长一次锁,时间要比 timeoutSeconds 小,否则没有意义
+ /// 等待锁释放超时时间,如果这个时间内获取不到锁,则 LockController 为 null
+ /// 自动延长锁超时时间,看门狗线程的超时时间为timeoutSeconds/2 , 在看门狗线程超时时间时自动延长锁的时间为timeoutSeconds。除非程序意外退出,否则永不超时。
+ /// CancellationToken 自动取消锁
+ ///
+ public LockController Lock(string name, int timeoutSeconds, int refrshTimeoutSeconds, int waitTimeoutSeconds, bool autoDelay = true, CancellationToken token = default)
+ {
+ if (refrshTimeoutSeconds == 0) throw new ArgumentException(nameof(refrshTimeoutSeconds), "刷新间隔时间不能为0");
+ if (waitTimeoutSeconds == 0) waitTimeoutSeconds = refrshTimeoutSeconds;
+
+ name = $"RedisClientLock:{name}";
+ var startTime = DateTime.Now;
+
+ // 规定时间内等待锁释放
+ while (DateTime.Now.Subtract(startTime).TotalSeconds < waitTimeoutSeconds)
+ {
+ var value = Guid.NewGuid().ToString();
+ if (SetNx(name, value, timeoutSeconds) == true)
+ {
+ return new LockController(this, name, value, timeoutSeconds, refrshTimeoutSeconds, autoDelay, token);
+ }
+ if (token.IsCancellationRequested) return null;
+ Thread.CurrentThread.Join(millisecondsTimeout: 3);
+ }
+ return null;
+ }
+
+ public class LockController : IDisposable
+ {
+
+ RedisClient _client;
+ string _name;
+ string _value;
+ int _timeoutSeconds;
+ Timer _autoDelayTimer;
+ private CancellationTokenSource _handleLostTokenSource;
+ private readonly CancellationToken _token;
+
+ ///
+ /// 当刷新锁时间的看门狗线程失去与Redis连接时,导致无法刷新延长锁时间时,触发此HandelLostToken Cancel
+ ///
+ public CancellationToken? HandleLostToken { get; }
+
+ internal LockController(RedisClient rds, string name, string value, int timeoutSeconds, double refreshSeconds, bool autoDelay, CancellationToken token)
+ {
+ _client = rds;
+ _name = name;
+ _value = value;
+ _timeoutSeconds = timeoutSeconds;
+ _token = token;
+ if (autoDelay)
+ {
+ _handleLostTokenSource = new CancellationTokenSource();
+ HandleLostToken = _handleLostTokenSource.Token;
+
+ var refreshMilli = (int)(refreshSeconds * 1000);
+ var timeoutMilli = timeoutSeconds * 1000;
+ _autoDelayTimer = new Timer(state2 => Refresh(timeoutMilli), null, refreshMilli, refreshMilli);
+ }
+ }
+
+ ///
+ /// 延长锁时间,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
+ ///
+ /// 延长的毫秒数
+ /// 成功/失败
+ public bool Delay(int milliseconds)
+ {
+ var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
local ttlva = redis.call('PTTL', KEYS[1])
redis.call('PEXPIRE', KEYS[1], ARGV[2] + ttlva)
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
- if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
- return ret;
- }
-
- ///
- /// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
- ///
- /// 刷新的毫秒数
- /// 成功/失败
- public bool Refresh(int milliseconds)
- {
- var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ return ret;
+ }
+
+ ///
+ /// 刷新锁时间,把key的ttl重新设置为milliseconds,锁在占用期内操作时返回true,若因锁超时被其他使用者占用则返回false
+ ///
+ /// 刷新的毫秒数
+ /// 成功/失败
+ public bool Refresh(int milliseconds)
+ {
+ if (_token.IsCancellationRequested)
+ {
+ _autoDelayTimer?.Dispose();
+ }
+
+ try
+ {
+ var ret = _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('PEXPIRE', KEYS[1], ARGV[2])
return 1
end
return 0", new[] { _name }, _value, milliseconds)?.ToString() == "1";
- if (ret == false) _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
-
- if (_token.IsCancellationRequested)
- {
- _autoDelayTimer?.Dispose();
- }
- return ret;
- }
-
- ///
- /// 释放分布式锁
- ///
- /// 成功/失败
- public bool Unlock()
- {
- _autoDelayTimer?.Dispose();
- return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
+ if (ret == false)
+ {
+ _handleLostTokenSource?.Cancel();
+ _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ }
+
+ return ret;
+ }
+ catch
+ {
+ _handleLostTokenSource?.Cancel();
+ _autoDelayTimer?.Dispose(); //未知情况,关闭定时器
+ return false;//这里必须要吞掉异常,否则会导致整个程序崩溃,因为Timer的异常没有地方去处理
+ }
+ }
+
+ ///
+ /// 释放分布式锁
+ ///
+ /// 成功/失败
+ public bool Unlock()
+ {
+ _handleLostTokenSource?.Dispose();
+ _autoDelayTimer?.Dispose();
+ return _client.Eval(@"local gva = redis.call('GET', KEYS[1])
if gva == ARGV[1] then
redis.call('DEL', KEYS[1])
return 1
end
return 0", new[] { _name }, _value)?.ToString() == "1";
- }
+ }
- public void Dispose() => this.Unlock();
- }
- }
+ public void Dispose() => this.Unlock();
+ }
+ }
}