Putting Lazy Tasks in a Cache, and Computing Only Once, When First Requested
This post is an update to an earlier post on caching Tasks and only executing them when first accessed. In that post, I used Task.Start()
to start the task when it was first accessed. This works, but there is another way, as suggested by Steven Cleary in the comments on that post.
It stores Lazy<Task<string>>
in the cache, and when the value is first accessed, the Value
property of the Lazy<T>
is accessed, which starts the task.
1using Microsoft.Extensions.Caching.Memory;
2
3var builder = WebApplication.CreateBuilder(args);
4var app = builder.Build();
5
6MemoryCache cache = new MemoryCache(new MemoryCacheOptions());
7
8for (int loop = 1; loop <= 20; loop++)
9{
10 int delay = loop; // this is important
11 Lazy<Task<string>> taskToRunLater = new Lazy<Task<string>>(() => DoWorkAsync(delay));
12 cache.Set(loop, taskToRunLater);
13}
14
15app.MapGet("/{id}", async (int id) => {
16 if(cache.TryGetValue(id, out Lazy<Task<string>>? doWorkAsyncTask) && doWorkAsyncTask != null)
17 {
18 return await doWorkAsyncTask.Value;
19 }
20 return $"Value {id} not found";
21});
22
23app.Run();
24
25async Task<string> DoWorkAsync(int number)
26{
27 Console.WriteLine($"Running DoWorkAsync with a {number} second delay");
28 DateTime started = DateTime.Now;
29 await Task.Delay(number * 1000);
30 return $"Started at {started:HH:MM:ss}, finished at {DateTime.Now:HH:MM:ss}. This value took {number} seconds to generate";
31}
- line 11 creates a
Lazy<Task<string>>
that will run the lambda that callsDoWorkAsync(delay)
when thetaskToRunLater.Value
property is accessed. - line 12 stores the
taskToRunLater
in the cache. - line 16 retrieves the
Lazy<Task<string>>
from the cache. - line 18 accesses the
Value
property of theLazy<Task<string>>
, which starts the task if it has not already been started, and returns theTask<string>
. If the task has already completed, it just returns theTask<string>
.
That’s it, most of the code remains the same as the earlier post.