我需要有一段代碼,它允許根據引數鍵同時僅由 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)
我在這里可以想象的所有情況都是執行緒安全的。請幫忙,我錯過了什么案例?
uj5u.com熱心網友回復:
你在這里有一個執行緒競爭,另一個任務試圖獲取相同的信號量,并在你獲取它時Release
- 即另一個執行緒正在等待semaphore.WaitAsync()
. 檢查CurrentCount
是一種競爭條件,它可以根據時間進行。檢查TryRemove
是無關緊要的,因為競爭執行緒已經得到了信號量——畢竟,它在等待WaitAsync()
.
uj5u.com熱心網友回復:
正如評論中所討論的,您在這里有幾個競爭條件。
- 執行緒 1 持有鎖,執行緒 2 正在等待
WaitAsync()
。執行緒 1 釋放鎖,然后semaphore.CurrentCount
在執行緒 2 能夠獲取它之前檢查 。 - 執行緒 1 持有鎖,釋放它,然后檢查
semaphore.CurrentCount
哪個通過。執行緒 2 進入GetValueWithBlockAsync
,呼叫Semaphores.GetOrAdd
并獲取信號量。執行緒 1 然后呼叫Semaphores.TryRemove
并處理信號量。
您確實需要鎖定從其中洗掉條目的決定Semaphores
- 沒有辦法解決這個問題。您也沒有辦法跟蹤是否有任何執行緒從中獲取信號量Semaphores
(并且當前正在等待它,或者還沒有到達那個點)。
一種方法是這樣做:擁有一個在所有人之間共享的鎖,但僅在獲取/創建信號量并決定是否釋放它時才需要。我們手動跟蹤當前有多少執行緒對特定信號量感興趣。當一個執行緒釋放信號量時,它會獲取共享鎖以檢查當前是否有其他人對該信號量感興趣,并且只有在沒有其他人感興趣時才釋放它。
private static readonly object semaphoresLock = new();
private static readonly Dictionary<string, State> semaphores = new();
private async Task<TModel> GetValueWithBlockAsync<TModel>(string valueKey, Func<Task<TModel>> valueAction)
{
State state;
lock (semaphoresLock)
{
if (!semaphores.TryGetValue(valueKey, out state))
{
state = new();
semaphores[valueKey] = state;
}
state.Count ;
}
try
{
await state.Semaphore.WaitAsync();
return await valueAction();
}
finally
{
state.Semaphore.Release();
lock (semaphoresLock)
{
state.Count--;
if (state.Count == 0)
{
semaphores.Remove(valueKey);
state.Semaphore.Dispose();
}
}
}
}
private class State
{
public int Count { get; set; }
public SemaphoreSlim Semaphore { get; } = new(1, 1);
}
當然,另一種選擇是讓Semaphores
成長。也許你有一個周期性的操作要通過并清除任何沒有被使用的東西,但這當然需要保護以確保執行緒不會突然對正在清除的信號量感興趣。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/457397.html