【发布时间】:2021-05-31 12:07:09
【问题描述】:
以下是我的问题。
我有一个 API 控制器,里面有一个 API 端点(资源)。
api/myResource/{id}/do-something
我正在开发一个中间件,它将根据一些业务规则限制对该资源的访问。在这个中间件中,我正在匹配传入的请求,我正在解析 URI,并且我想允许访问它(让管道流动)或简单地返回 412 状态代码以防达到允许的线程数限制 FOR给定的资源(例如)
api/myResource/1/do-something /// should allow 2 concurrent accesses.
api/myResource/2/do-something /// should allow 10 concurrent accesses.
api/myResource/3/do-something /// should allow 1 concurrent accesses.
为此,我已经开始实施我将附上的解决方案。
internal class AsyncLock<TKey>
{
private static readonly ConcurrentDictionary<TKey, SemaphoreSlim> _safeSemaphores
= new ConcurrentDictionary<TKey, SemaphoreSlim>();
internal async Task<SemaphoreSlim> TryLockAsync(TKey key, int maxConcurrentCount)
{
if (!_safeSemaphores.TryGetValue(key, out SemaphoreSlim semaphore))
{
semaphore = new SemaphoreSlim(maxConcurrentCount, maxConcurrentCount);
_safeSemaphores.TryAdd(key, semaphore);
}
await semaphore.WaitAsync();
return semaphore;
}
internal SemaphoreSlim TryLock(TKey key, int maxConcurrentCount)
{
if (!_safeSemaphores.TryGetValue(key, out SemaphoreSlim semaphore))
{
semaphore = new SemaphoreSlim(maxConcurrentCount, maxConcurrentCount);
_safeSemaphores.TryAdd(key, semaphore);
}
semaphore.Wait();
return semaphore;
}
}
这就是它的使用方式(它允许2个并发访问,当然这是这个问题的主题,它不会是硬编码的2而是在管道的早期确定)
AsyncLock<string> _lock = new AsyncLock<string>();
SemaphoreSlim semaphore = await _lock .TryLockAsync(key, 2);
if (semaphore.CurrentCount != 0)
{
context.Response.StatusCode = 200;
//await _next(context);
semaphore.Release();
}
else
{
context.Response.StatusCode = 412;
}
它的行为方式是不可预测的。我正在使用 4 个线程进行测试,有时它按预期工作,有时它们都返回 200,有时它们都卡住了,我的意思是每次都是组合。
如果能帮我解决这个问题,我将不胜感激。
【问题讨论】:
-
嗨,有趣,也许使用
Interlocked.CompareExchange- 如果值已经更新,请跳过。 -
您知道在锁中使用异步代码会导致应用程序无响应吗?异步代码主要用于 IO 操作。 (文件/网络/等)如果异步代码试图下载一个大文件怎么办?锁会导致拥塞,因此请确保它们尽可能短。我建议审查设计并将异步代码放在锁之外。
-
为了响应单个 API 调用,是否有可能需要获取多个信号量?还是保证每个
api/myResource/X/do-something将只获得一个信号量?在第二种情况下,您可能需要KeyedSemaphore。您可以查看这个问题作为起点:Asynchronous locking based on a key -
@TheodorZoulias,我知道这个实现,实际上这是我开始的地方。您的假设是,
api/myResource/X/do-something将被单个信号量获取是正确的。 -
那里发布了一些可靠的实现。例如,您可以取Stephen Cleary's solution,并将
GetOrCreate(object key)更改为GetOrCreate(object key, int maximumConcurrency),并使用item = new RefCounted<SemaphoreSlim>(new SemaphoreSlim(1, 1));行中的参数,替换1s。
标签: c# multithreading asynchronous concurrency locking