【发布时间】:2020-01-17 20:30:45
【问题描述】:
我有 CacheService,它在 MemoryCache 中存储一个集合,然后从存储在缓存中的集合中找到一个元素。鉴于多线程环境,我想确保只有一个 Worker 可以将集合存储在缓存中并找到它。所以我使用lock 来同步调用并使线程安全。
public class MyCacheService
{
private readonly IMemoryCache _memoryCache = null;
static object myLock = new object();
public MyCacheService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache ?? throw new ArgumentNullException(nameof(memoryCache));
}
public async Task<Job> Find(int key, string title, int[] skills, Func<int, Task<List<Job>>> getJobs)
{
lock (myLock)
{
List<Job> cachedJobs = null;
if (!_memoryCache.TryGetValue(key, out cachedJobs))
{
// compilation error here 'cannot await in the body of a lock statement'
var jobs = await getJobs(key);
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(30));
cachedJobs = _memoryCache.Set(key, cachedJobs, cacheEntryOptions);
}
if (cachedJobs != null)
{
var job = cachedJobs.Where(j => j.Title == title &&
!j.Skills.Except(skills).Any())
.FirstOrDefault();
if (job == null)
{
return null;
}
cachedJobs.Remove(job);
return job;
}
return null;
}
}
}
getJobs 委托是从数据库中获取作业的异步调用。所以我收到错误cannot await in the body of a lock statement
我明白为什么我会出错。我可以使用getJobs(key).GetAwaiter().GetResult() 来解决错误
有LazyCache 保证对要缓存其结果的委托进行单一评估,我们可以使用异步委托,但我没有使用它
还有其他选择吗?
更新 1
我尝试按照建议使用SemaphoreSlim,但它没有按预期工作。在
下面的 DEMO 我总共有 10000 个工作(5000 个 BASIC 工作和 5000 个 Master 工作)和总共 200 个工人。前 100 个工人 (1-100) 用于 BASIC 作业,101 到 200 个工人用于 Master 作业。
期望从 1 到 100 的任何工人都将获得 BASIC 工作,而 101-200 将获得 MASTER 工作
SemaphoreSlim 似乎没有按预期工作。使用这种方法,所有 5000 个 BASIC 作业总是分配给 ID 为 1 的 Worker。并且所有 MASTER 作业总是被分配给 ID 为 101 的 Worker
DEMO 使用 SemaphoreSlim
只要我没有在锁内使用异步方法,我使用 C# 锁的初始方法似乎可以按预期工作
DEMO 使用 lock
【问题讨论】:
-
GetAwaiter().GetResult()不是解决此问题的好方法,因为您现在正在阻塞异步结果,这完全破坏了异步过程。您应该避免在等待结果时锁定某些东西,但如果这样做,您可以使用其他一些锁定机制,例如SemaphoreSlim. -
另一种方法可能是不缓存实际结果,而是为结果缓存 tasks。因此,当您没有缓存值时,您正在存储任务,并且所有其他请求(您通常会阻止)将获得相同的结果任务。
标签: asp.net-core .net-core memorycache lazycache