1

I wrote a method that should get some information from a web service. I wanna avoid extra calls to the service, so I'm trying to put the information in the MemoryCache according to this post.

The difference is that I don't have "SomeHeavyAndExpensiveCalculation" at client, but I delegate the work to web service. So I want to await the call.

As I understand with current implementation I could have multiple requests to the web server, because I cannot await the request inside the lock and that's why it's moved out of the lock.

Is there a better solution?

Thanks.

 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;
                }
Community
  • 1
  • 1
3615
  • 3,787
  • 3
  • 20
  • 35

1 Answers1

6

Use a SemaphoreSlim so you can lock asynchronously and put the lock block before you make the call to the web service.

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();
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • I think this technique is called double-check locking and it's actually not that thread safe, at least with ordinary lock. Don't know if it is better with SemaphoreSlim – Ilya Chernomordik May 14 '19 at 12:30