I want to cache calculation results in a ConcurrentDictionary<TKey,TValue>. Several threads may query the cache for an entry and generate it if it does not exist.
Since GetOrAdd(TKey, Func<TKey,TValue>) is not atomic, I think I should use GetOrAdd(TKey, TValue) with Task<CacheItem> as TValue.
So, when a thread wants to query a cache item, it generates a cold task coldTask, that is a task, which is not started, and potentially generates the the item, calls var cacheTask = cache.GetOrAdd(key, coldTask) for some key object, and then checks whether cacheTask is started or even has a result. If cacheTask is not started, the calling thread starts the task.
Is this a valid approach in principle?
One problem that remains is that
if(cacheTask.Status == Status.Created)
  cacheTask.Start();
is not atomic, so the cacheTask may be started from another thread, before cacheTask.Start() is called here.
Is
try {
if(cacheTask.Status == Status.Created)
  cacheTask.Start();
} catch {}
a valid workaround?