我在c#和线程的早期工作,我需要一个数据结构,可以像字典一样存储键值对,但在添加一定时间后,我希望值"过期",或者从字典中删除。
我已经提出了一个实现,似乎工作,但我想知道是否有可能有任何问题,当它在多线程环境中使用。
- 我目前的执行有任何直接问题吗?
- 由于字典的所有使用都受到信号量的保护,这个实现是否被认为是线程安全的?
public class TokenCache<TKey, TValue>
where TKey : IEquatable<TKey>
where TValue : class
{
private readonly TimeSpan expirationTime;
private readonly SemaphoreSlim semaphore;
private readonly Dictionary<TKey, TValue> cache;
public TokenCache(TimeSpan expirationTime)
{
this.expirationTime = expirationTime;
this.semaphore = new SemaphoreSlim(1, 1);
this.cache = new Dictionary<TKey, TValue>();
}
public void Add(TKey key, TValue value)
{
this.semaphore.Wait();
try
{
this.cache[key] = value;
Task
.Run(() => Thread.Sleep(this.expirationTime))
.ContinueWith(t =>
{
this.Remove(key);
});
}
finally
{
this.semaphore.Release();
}
}
public void Remove(TKey key)
{
this.semaphore.Wait();
try
{
this.cache.Remove(key);
}
finally
{
this.semaphore.Release();
}
}
public TValue GetValueOrDefault(TKey key)
{
this.semaphore.Wait();
try
{
if (this.cache.TryGetValue(key, out var value))
{
return value;
}
return null;
}
finally
{
this.semaphore.Release();
}
}
}
我看到的问题没有特别的顺序(可能有更多):
任务。运行
Task.Run(() => Thread.Sleep(...))
是一个非常糟糕的主意。ThreadPool
包含运行Task
s的线程,其线程数量有限(它可以增长,但增长非常缓慢!!),并且通过调用Task.Run(() => Thread.Sleep(...))
而不是Task.Delay(...)
,您在等待期间浪费了一个ThreadPool
线程。Task.Delay(...)
不占用线程
项目过期错误
另一个问题是你的Add
方法。考虑以下内容:
- 创建
TokenCache
的实例,例如通过new TokenCache<int, string>(TimeSpan.FromMinutes(1))
- 呼叫
Add(10, "ten")
- 50秒后,呼叫
Remove(10)
- 5秒后,呼叫
Add(10, "The Number 10");
- 因为你已经写了你的
Add
方法,你现在要删除值"The Number 10"
后,只有5秒
毫无意义的信号而且,我真的不明白使用SemaphoreSlim
的意义。在这种情况下,您可以在private readonly object locker = new();
lock
关键字