如何在锁内使用异步方法

时间:2020-01-17 20:30:45

标签: asp.net-core .net-core memorycache lazycache

我有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个主工作),共有200个工人。前100名工人(1-100名)从事BASIC工作,而101至200名工人从事硕士工作。

期望从1到100的任何工人将获得BASIC工作,而101-200的任何工人将获得主工作

SemaphoreSlim似乎无法正常工作。通过这种方法,总是将所有5000个BASIC作业分配给ID为1的Worker。而且,所有MASTER作业始终分配给ID为101的Worker

DEMO使用SemaphoreSlim

只要不在锁内使用异步方法,我最初使用C#锁的方法似乎就可以正常工作

DEMO使用lock

3 个答案:

答案 0 :(得分:3)

鉴于多线程环境,我想确保只有一个Worker可以将集合存储在缓存中并找到它。

您当前的(尝试过的)解决方案锁很粗:如果一个请求尝试查找具有给定密钥的作业,则该请求可能会被另一个正在数据库中查询不同密钥的请求所阻止。也就是说,可以使用SempahoreSlim完成现有代码的字面翻译:

  static SemaphoreSlim myLock = new SemaphoreSlim(1);

  public async Task<Job> Find(int key, string title, int[] skills, Func<int, Task<List<Job>>> getJobs)
  {
    await myLock.WaitAsync();
    try
    {
      ...
    }
    finally
    {
      myLock.Release();
    }
  }

答案 1 :(得分:0)

您可以使用支持异步委托的IDistributedCache。并使用Redis进行缓存。

Distributed caching in ASP.NET core

答案 2 :(得分:0)

@Stephen Cleary的答案仍然有效。您的演示代码只是没有像您期望的那样在后台线程上执行所有辅助任务,因此您基本上是在等待(每种作业类型的)第一个任务完成。

在演示代码中,您可以将所有工作程序任务作为后台任务运行,如下所示:

// your other code...
workers.AddRange(masterWorkers);
              
var start = DateTime.Now;
var tasks = workers.Select(s => Task.Run(s.DoWorkAsync)).ToArray();

Task.WaitAll(tasks);

var end = DateTime.Now;
// your other code

提琴:https://dotnetfiddle.net/GPasHM

这与Microsoft文档中的示例类似;尽管try{} finally{}的一般建议缺少?。