как использовать метод asyn c внутри блокировки - PullRequest
0 голосов
/ 17 января 2020

У меня есть CacheService, который хранит коллекцию в MemoryCache, а затем находит элемент из коллекции, который хранится в кэше. Учитывая многопоточность, я хочу убедиться, что только один работник может сохранить коллекцию в кеше и найти ее. Поэтому я использую 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 является asyn c вызовом для получения заданий из базы данных. Так что я получаю ошибку cannot await in the body of a lock statement

Я понял, почему я получаю ошибку. Я могу использовать getJobs(key).GetAwaiter().GetResult() для устранения ошибки

Существует LazyCache , который гарантирует однократную оценку делегата, результаты которого вы хотите кэшировать, и мы можем использовать asyn c делегат, но у меня нет использовал его

есть ли другие варианты?

ОБНОВЛЕНИЕ 1
Я попытался использовать SemaphoreSlim, как предложено, однако это не работает, как ожидалось. В DEMO ниже у меня есть 10000 рабочих мест (5000 BASI C рабочих мест и 5000 рабочих мест мастера) и всего 200 рабочих. Первые 100 рабочих (1-100) предназначены для рабочих мест BASI C, а 101 - 200 рабочих - для мастеров.

Ожидается, что любой работник от 1 до 100 получит работу BASI C, а 101-200 - получить MASTER задание

SemaphoreSlim не работает должным образом. При таком подходе все 5000 заданий BASI C всегда присваиваются работнику с идентификатором 1. И все задания MASTER всегда назначаются для Worker с идентификатором 101

DEMO с использованием SemaphoreSlim

Мой первоначальный подход с использованием блокировки C#, кажется, работает, как и ожидалось, пока поскольку я не использую метод asyn c внутри блокировки

DEMO используя lock

Ответы [ 2 ]

2 голосов
/ 18 января 2020

Учитывая многопоточную среду, я хочу убедиться, что только один работник может сохранить коллекцию в кеше и найти ее.

Ваше текущее (попытанное) решение имеет очень грубую блокировку : если один запрос пытается найти работу с данным ключом, он может быть заблокирован другим запросом, который запрашивает базу данных для заданий для другого ключа. Тем не менее, буквальный перевод вашего существующего кода может быть сделан с помощью 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();
    }
  }
0 голосов
/ 18 января 2020

Вы можете использовать IDistributedCache, который поддерживает делегирование asyn c. И использование Redis для реализации кэша.

Распределенное кэширование в ASP. NET core

...