-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
在`DemoTask.cs`中,对`DemoTask`类的构造函数进行了修改,添加了一个新的参数`ILocalLock @lock`。同时,重写了`ExecuteAsync`方法,添加了使用`ILocalLock`进行锁定的逻辑,以防止任务重复执行。
- Loading branch information
Showing
6 changed files
with
255 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
namespace Biwen.QuickApi.Infrastructure.Locking | ||
{ | ||
/// <summary> | ||
/// ILocalLock | ||
/// </summary> | ||
public interface ILocalLock : ILock | ||
{ | ||
} | ||
|
||
public interface ILock | ||
{ | ||
/// <summary> | ||
/// Waits indefinitely until acquiring a named lock with a given expiration for the current tenant. | ||
/// After 'expiration' the lock is auto released, a null value is equivalent to 'TimeSpan.MaxValue'. | ||
/// </summary> | ||
Task<ILocker> AcquireLockAsync(string key, TimeSpan? expiration = null); | ||
|
||
/// <summary> | ||
/// Tries to acquire a named lock in a given timeout with a given expiration for the current tenant. | ||
/// After 'expiration' the lock is auto released, a null value is equivalent to 'TimeSpan.MaxValue'. | ||
/// </summary> | ||
Task<(ILocker locker, bool locked)> TryAcquireLockAsync(string key, TimeSpan timeout, TimeSpan? expiration = null); | ||
|
||
/// <summary> | ||
/// Whether a named lock is already acquired. | ||
/// </summary> | ||
Task<bool> IsLockAcquiredAsync(string key); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Biwen.QuickApi.Infrastructure.Locking | ||
{ | ||
public interface ILocker : IDisposable, IAsyncDisposable | ||
{ | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
//tks to:https://github.com/OrchardCMS/OrchardCore/tree/main/src/OrchardCore/OrchardCore/Locking | ||
|
||
namespace Biwen.QuickApi.Infrastructure.Locking | ||
{ | ||
//var timeout = TimeSpan.FromMilliseconds(20_000); | ||
//(var locker, var locked) = await _distributedLock.TryAcquireLockAsync("SITEMAPS_UPDATE_LOCK", timeout, timeout); | ||
//if (!locked) | ||
//{ | ||
// throw new TimeoutException($"Couldn't acquire a lock to update the sitemap within {timeout.Seconds} seconds."); | ||
//} | ||
|
||
//using (locker) | ||
//{ | ||
// // Do the work | ||
//} | ||
|
||
/// <summary> | ||
/// LocalLock | ||
/// </summary> | ||
public sealed class LocalLock : ILocalLock, IDisposable | ||
{ | ||
private readonly ILogger _logger; | ||
|
||
private readonly Dictionary<string, Semaphore> _semaphores = []; | ||
|
||
public LocalLock(ILogger<LocalLock> logger) | ||
{ | ||
_logger = logger; | ||
} | ||
|
||
/// <summary> | ||
/// Waits indefinitely until acquiring a named lock with a given expiration for the current tenant. | ||
/// After 'expiration' the lock is auto released, a null value is equivalent to 'TimeSpan.MaxValue'. | ||
/// </summary> | ||
public async Task<ILocker> AcquireLockAsync(string key, TimeSpan? expiration = null) | ||
{ | ||
var semaphore = GetOrCreateSemaphore(key); | ||
await semaphore.Value.WaitAsync(); | ||
|
||
return new Locker(this, semaphore, expiration); | ||
} | ||
|
||
/// <summary> | ||
/// Tries to acquire a named lock in a given timeout with a given expiration for the current tenant. | ||
/// After 'expiration' the lock is auto released, a null value is equivalent to 'TimeSpan.MaxValue'. | ||
/// </summary> | ||
public async Task<(ILocker locker, bool locked)> TryAcquireLockAsync(string key, TimeSpan timeout, TimeSpan? expiration = null) | ||
{ | ||
var semaphore = GetOrCreateSemaphore(key); | ||
|
||
if (await semaphore.Value.WaitAsync(timeout != TimeSpan.MaxValue ? timeout : Timeout.InfiniteTimeSpan)) | ||
{ | ||
return (new Locker(this, semaphore, expiration), true); | ||
} | ||
|
||
if (_logger.IsEnabled(LogLevel.Debug)) | ||
{ | ||
_logger.LogDebug("Timeout elapsed before acquiring the named lock '{LockName}' after the given timeout of '{Timeout}'.", | ||
key, timeout.ToString()); | ||
} | ||
|
||
return (null!, false); | ||
} | ||
|
||
public Task<bool> IsLockAcquiredAsync(string key) | ||
{ | ||
lock (_semaphores) | ||
{ | ||
if (_semaphores.TryGetValue(key, out var semaphore)) | ||
{ | ||
return Task.FromResult(semaphore.Value.CurrentCount == 0); | ||
} | ||
|
||
return Task.FromResult(false); | ||
} | ||
} | ||
|
||
private Semaphore GetOrCreateSemaphore(string key) | ||
{ | ||
lock (_semaphores) | ||
{ | ||
if (_semaphores.TryGetValue(key, out var semaphore)) | ||
{ | ||
semaphore.RefCount++; | ||
} | ||
else | ||
{ | ||
semaphore = new Semaphore(key, new SemaphoreSlim(1)); | ||
_semaphores[key] = semaphore; | ||
} | ||
|
||
return semaphore; | ||
} | ||
} | ||
|
||
private sealed class Semaphore | ||
{ | ||
public Semaphore(string key, SemaphoreSlim value) | ||
{ | ||
Key = key; | ||
Value = value; | ||
RefCount = 1; | ||
} | ||
|
||
internal string Key { get; } | ||
internal SemaphoreSlim Value { get; } | ||
internal int RefCount { get; set; } | ||
} | ||
|
||
private sealed class Locker : ILocker | ||
{ | ||
private readonly LocalLock _localLock; | ||
private readonly Semaphore _semaphore; | ||
private readonly CancellationTokenSource _cts = null!; | ||
private volatile int _released; | ||
private bool _disposed; | ||
|
||
public Locker(LocalLock localLock, Semaphore semaphore, TimeSpan? expiration) | ||
{ | ||
_localLock = localLock; | ||
_semaphore = semaphore; | ||
|
||
if (expiration.HasValue && expiration.Value != TimeSpan.MaxValue) | ||
{ | ||
_cts = new CancellationTokenSource(expiration.Value); | ||
_cts.Token.Register(Release); | ||
} | ||
} | ||
|
||
private void Release() | ||
{ | ||
if (Interlocked.Exchange(ref _released, 1) == 0) | ||
{ | ||
lock (_localLock._semaphores) | ||
{ | ||
if (_localLock._semaphores.TryGetValue(_semaphore.Key, out var semaphore)) | ||
{ | ||
semaphore.RefCount--; | ||
|
||
if (semaphore.RefCount == 0) | ||
{ | ||
_localLock._semaphores.Remove(_semaphore.Key); | ||
} | ||
} | ||
} | ||
|
||
_semaphore.Value.Release(); | ||
} | ||
} | ||
|
||
public ValueTask DisposeAsync() | ||
{ | ||
Dispose(); | ||
return default; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
if (_disposed) | ||
{ | ||
return; | ||
} | ||
|
||
_disposed = true; | ||
|
||
_cts?.Dispose(); | ||
|
||
Release(); | ||
} | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
var semaphores = _semaphores.Values.ToArray(); | ||
|
||
foreach (var semaphore in semaphores) | ||
{ | ||
semaphore.Value.Dispose(); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters