信号量的多线程问题
我需要有一段代码,该代码只允许基于参数键同时由 1 个线程执行:
private static readonly ConcurrentDictionary<string, SemaphoreSlim> Semaphores = new();
private async Task<TModel> GetValueWithBlockAsync<TModel>(string valueKey, Func<Task<TModel>> valueAction)
{
var semaphore = Semaphores.GetOrAdd(valueKey, s => new SemaphoreSlim(1, 1));
try
{
await semaphore.WaitAsync();
return await valueAction();
}
finally
{
semaphore.Release(); // Exception here - System.ObjectDisposedException
if (semaphore.CurrentCount > 0 && Semaphores.TryRemove(valueKey, out semaphore))
{
semaphore?.Dispose();
}
}
}
有时我会收到错误:
The semaphore has been disposed. : System.ObjectDisposedException: The semaphore has been disposed.
at System.Threading.SemaphoreSlim.CheckDispose()
at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
at Project.GetValueWithBlockAsync[TModel](String valueKey, Func`1 valueAction)
我在这里可以想象的所有情况都是线程安全的。请帮忙,我错过了什么案例?
I need to have the piece of code which allowed to execute only by 1 thread at the same time based on parameter key:
private static readonly ConcurrentDictionary<string, SemaphoreSlim> Semaphores = new();
private async Task<TModel> GetValueWithBlockAsync<TModel>(string valueKey, Func<Task<TModel>> valueAction)
{
var semaphore = Semaphores.GetOrAdd(valueKey, s => new SemaphoreSlim(1, 1));
try
{
await semaphore.WaitAsync();
return await valueAction();
}
finally
{
semaphore.Release(); // Exception here - System.ObjectDisposedException
if (semaphore.CurrentCount > 0 && Semaphores.TryRemove(valueKey, out semaphore))
{
semaphore?.Dispose();
}
}
}
Time to time I got the error:
The semaphore has been disposed. : System.ObjectDisposedException: The semaphore has been disposed.
at System.Threading.SemaphoreSlim.CheckDispose()
at System.Threading.SemaphoreSlim.Release(Int32 releaseCount)
at Project.GetValueWithBlockAsync[TModel](String valueKey, Func`1 valueAction)
All cases that I can imagine here are thread safety. Please help, what case I missed?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您在这里有一个线程竞赛,另一个任务正在尝试获取相同的信号量,并在您
版本
- ie另一个线程等待semaphore.waitasync()
时获取它。 。针对CurrentCount
的检查是一种竞赛条件,并且可以根据时间来进行任何任务。检查TryRemove
的检查是无关紧要的,因为竞争性线程已经 删除了信号量 - 毕竟,等待waitaSync()
。You have a thread race here, where another task is trying to acquire the same semaphore, and acquires it when you
Release
- i.e. another thread is awaiting thesemaphore.WaitAsync()
. The check againstCurrentCount
is a race condition, and it could go either way depending on timing. The check forTryRemove
is irrelevant, as the competing thread already got the semaphore out - it was, after all, awaiting theWaitAsync()
.如评论中所述,您在这里有几个种族条件。
waitasync()
。线程1释放锁,然后在Thread 2之前检查SmemAphore.CurrentCount
。Semaphore.currentCount
通过。线程2输入getValueWithBlockAsync
,呼叫semaphores.getoradd
并获取了信号量。线程1然后调用信号量。tryremove
并递给信号量。您确实需要锁定从
Semaphores
中删除条目的决定 - 这是没有办法的。您也没有一种方法来跟踪是否有任何线程从semaphores
中获取了信号量(并且当前正在等待它,或者还没有达到这一点)。一种方法是做这样的事情:拥有一个在每个人之间共享的锁,但是只有在获取/创建信号量并决定是否处置时才需要。我们手动跟踪目前有多少线程对特定信号量有兴趣。当线程发布信号量时,它将获取共享锁,以检查其他人当前是否对该信号量有兴趣,并只有在没有其他人的情况下才能将其处置。
当然,另一个选择是让
信号量
增长。也许您有定期操作可以进行并清除任何未使用的东西,但是这当然需要受到保护,以确保线程不会突然对被清除的信号量感兴趣。As discussed in the comments, you have a couple of race conditions here.
WaitAsync()
. Thread 1 releases the lock, and then checkssemaphore.CurrentCount
, before Thread 2 is able to acquire it.semaphore.CurrentCount
which passes. Thread 2 entersGetValueWithBlockAsync
, callsSemaphores.GetOrAdd
and fetches the semaphore. Thread 1 then callsSemaphores.TryRemove
and diposes the semaphore.You really need locking around the decision to remove an entry from
Semaphores
-- there's no way around this. You also don't have a way of tracking whether any threads have fetched a semaphore fromSemaphores
(and are either currently waiting on it, or haven't yet got to that point).One way is to do something like this: have a lock which is shared between everyone, but which is only needed when fetching/creating a semaphore, and deciding whether to dispose it. We manually keep track of how many threads currently have an interest in a particular semaphore. When a thread has released the semaphore, it then acquires the shared lock to check whether anyone else currently has an interest in that semaphore, and disposes it only if noone else does.
The other option, of course, is to let
Semaphores
grow. Maybe you have a periodic operation to go through and clear out anything which isn't being used, but this will of course need to be protected to ensure that a thread doesn't suddenly become interested in a semaphore which is being cleared up.