Блокировка при возврате записи кэша из асинхронного запроса

Я написал метод, который должен получить некоторую информацию от веб-службы. Я хочу избежать дополнительных вызовов службы, поэтому я пытаюсь поместить информацию в MemoryCache согласно этому сообщению.

Разница в том, что у меня нет "SomeHeavyAndExpensiveCalculation" на клиенте, а я делегирую работу веб-сервису. Поэтому я хочу дождаться звонка.

Насколько я понимаю, с текущей реализацией у меня может быть несколько запросов к веб-серверу, потому что я не могу ждать запроса внутри блокировки, и поэтому он перемещен из блокировки.

Есть ли лучшее решение?

Спасибо.

 private static readonly object CompanyInfoLock = new object(); 

 public async Task<CompanyDto> GetCompanyInfo()
                {
                    const string cacheKey = "_COMPANYINFO_";

                    CompanyDto companyInfo = MemoryCache.Default[cacheKey] as CompanyDto;
                    if (companyInfo != null) return companyInfo;

                    CompanyDto company = await _userManagementService.InvokeAsync(x => x.GetCompanyAsync(AppPrincipal.Current.CurrentCompanyId));

                    lock (CompanyInfoLock)
                    {
                        companyInfo = MemoryCache.Default[cacheKey] as CompanyDto;
                        if (companyInfo != null) return companyInfo;
                        MemoryCache.Default.Add(cacheKey, company, new CacheItemPolicy
                        {
                            SlidingExpiration = new TimeSpan(2, 0, 0)
                        });
                    }

                    return company;
                }

person 3615    schedule 18.02.2016    source источник
comment
stackoverflow.com/questions/31831860/   -  person Ohad Schneider    schedule 08.07.2017


Ответы (1)


Используйте SemaphoreSlim, чтобы вы можете заблокировать асинхронно и поместить блокировку перед вызовом веб-службы.

private static readonly SemaphoreSlim CompanyInfoLock = new SemaphoreSlim (1); 

public async Task<CompanyDto> GetCompanyInfo()
{
    const string cacheKey = "_COMPANYINFO_";
    CompanyDto companyInfo;

    companyInfo = MemoryCache.Default[cacheKey] as CompanyDto;
    if (companyInfo != null) return companyInfo;

    await CompanyInfoLock.WaitAsync();
    try
    {
        //Check a 2nd time inside the lock to see if the cache has the data.
        companyInfo = MemoryCache.Default[cacheKey] as CompanyDto;
        if (companyInfo != null) return companyInfo;

        companyInfo = await _userManagementService.InvokeAsync(x => x.GetCompanyAsync(AppPrincipal.Current.CurrentCompanyId));

        MemoryCache.Default.Add(cacheKey, companyInfo, new CacheItemPolicy
        {
            SlidingExpiration = new TimeSpan(2, 0, 0)
        });

        return companyInfo;
    }
    finally
    {
        CompanyInfoLock.Release();
    }
}
person Scott Chamberlain    schedule 18.02.2016
comment
Я думаю, что этот метод называется блокировкой с двойной проверкой, и на самом деле он не является потокобезопасным, по крайней мере, с обычной блокировкой. Не знаю, лучше ли с SemaphoreSlim - person Ilya Chernomordik; 14.05.2019