0

I have an ASP.NET web app, which calls an expensive operation and needs to cache the data. The call to the operation needs to be made only one time, even if there are concurrent requests.

The idea is that I keep the data in a ConcurrentDictionary. The first time any request access the dictionary, a Lazy record is inserted to defer the work for later. Any subsequent requests should get back the same record. Also the expensive operation uses await because of usage of HttpClient.

I tried to implement this with the following code

private static readonly ConcurrentDictionary<Guid, Lazy<Task<List<ProductsList>>>> cache = 
    new ConcurrentDictionary<Guid, Lazy<Task<List<ProductsList>>>>();

public async Task<List<ProductsList>> Get(AnalysisParams aParams, string refUrlApi)
{
    return await cache.GetOrAdd(aParams.Project_ID, (pid) => new Lazy<Task<List<ProductsList>>>(
        async () => await Task.Run(async() => await Utils.GetProductsList(aParams, refUrlApi))
    )).Value;
}

However when I check my logs, I can see that the expensive operation is called multiple times through the day instead of once. The instance where the web app is running is not restarted according to the admin.

Maybe I have messed up with the async/await? How can I achieve what I need?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
alfoks
  • 4,324
  • 4
  • 29
  • 44
  • 1
    [This article](https://endjin.com/blog/2023/01/dotnet-csharp-lazy-async-initialization) may be of interest. – Matthew Watson Feb 02 '23 at 10:44
  • IIS will recycle your app every 19 hrs by default, so if you're only seeing one or two requests per guid per day, that would be expected. – Stephen Cleary Feb 02 '23 at 11:49
  • @StephenCleary For today for example I can see 50+ calls. I know you are the guru when it comes to tasks. Do you see any fault with my code? – alfoks Feb 02 '23 at 12:14
  • @alfoks: I don't see anything that would cause the behavior you're seeing (I assume you mean 50+ calls *for the same guid*). The use of `Lazy` is unusual here but it would *guarantee* there's only one request per guid. There's also a problem with exceptions (as written, any error will cause all future same-guid requests to error). But I don't see how this code would allow multiple calls for the same guid. – Stephen Cleary Feb 02 '23 at 15:06
  • @StephenCleary Yes 50+ for the same Guid, thanks. I've added in the logs the process id to rule out the possibility the instance is being restarted. – alfoks Feb 02 '23 at 16:07
  • Related: [ConcurrentDictionary GetOrAdd async](https://stackoverflow.com/questions/54117652/concurrentdictionary-getoradd-async). – Theodor Zoulias Feb 02 '23 at 17:56
  • As a side note, all three `async` delegates in your code could safely [elide](https://blog.stephencleary.com/2016/12/eliding-async-await.html) the async/await. Regarding the main issue, I am not seeing any obvious reason for the `GetProductsList` to be invoked more than once for the same key. What is the `AnalysisParams`? Is it class or struct? Is it mutable or immutable? – Theodor Zoulias Feb 02 '23 at 18:26
  • @TheodorZoulias it's a standard public class, with some public get/set properties. Not sealed and without readonly fields. Is it possible that the garbage collector releases the dictionary? – alfoks Feb 03 '23 at 08:16
  • No, static readonly fields retains their values for the whole lifetime of the process. So these values are rooted, preventing the GC from recycling them. Regarding the `AnalysisParams` class, is it mutated anywhere? Specifically is it mutated inside the `Utils.GetProductsList` method? – Theodor Zoulias Feb 03 '23 at 08:20
  • @TheodorZoulias Inside `Utils.GetProductsList` it is not mutated. – alfoks Feb 03 '23 at 09:11
  • You could try making the property `AnalysisParams.Project_ID` readonly, and see if the problem persists. – Theodor Zoulias Feb 03 '23 at 16:19
  • Regarding static variables in ASP.NET: [Scope of static variables in ASP.NET sites](https://stackoverflow.com/questions/17390248/scope-of-static-variables-in-asp-net-sites). But be aware that in .NET Framework you can have multiple instances of a static in case the process has multiple `AppDomain`s. – Theodor Zoulias Feb 04 '23 at 02:59

0 Answers0