4

I've got a caching class that uses cold (unstarted) tasks to avoid running the expensive thing multiple times.

public class AsyncConcurrentDictionary<TKey, TValue> : System.Collections.Concurrent.ConcurrentDictionary<TKey, Task<TValue>>
{
    internal Task<TValue> GetOrAddAsync(TKey key, Task<TValue> newTask)
    {
        var cachedTask = base.GetOrAdd(key, newTask);

        if (cachedTask == newTask && cachedTask.Status == TaskStatus.Created) // We won! our task is now the cached task, so run it 
            cachedTask.Start();

        return cachedTask;
    }
}

This works great right up until your task is actually implemented using C#5's await, ala

cache.GetOrAddAsync("key", new Task(async () => {
  var r = await AsyncOperation();
  return r.FastSynchronousTransform();
}));)`

Now it looks like TaskExtensions.Unwrap() does exactly what I need by turning Task<Task<T>> into a Task<T>, but it seems that wrapper it returns doesn't actually support Start() - it throws an exception.

TaskCompletionSource (my go to for slightly special Task needs) doesn't seem to have any facilities for this sort of thing either.

Is there an alternative to TaskExtensions.Unwrap() that supports "cold tasks"?

Fowl
  • 4,940
  • 2
  • 26
  • 43
  • why don't you rephrase your question? i have read it multiple times and still unable to understand what you want to achieve. perhaps same is the case with others that is why you haven't got any answer as yet – Ehsan Aug 13 '13 at 06:26
  • What part don't you understand? `Task.Unwrap` returns a "Promise style" task which are already started, whereas I need `Task` instances to be unstarted so I can coalesce them in my cache. – Fowl Aug 13 '13 at 06:45

1 Answers1

7

All you need to do is to keep the Task before unwrapping it around and start that:

public Task<TValue> GetOrAddAsync(TKey key, Func<Task<TValue>> taskFunc)
{
    Task<Task<TValue>> wrappedTask = new Task<Task<TValue>>(taskFunc);
    Task<TValue> unwrappedTask = wrappedTask.Unwrap();

    Task<TValue> cachedTask = base.GetOrAdd(key, unwrappedTask);

    if (cachedTask == unwrappedTask)
        wrappedTask.Start();

    return cachedTask;
}

Usage:

cache.GetOrAddAsync(
    "key", async () =>
    {
        var r = await AsyncOperation();
        return r.FastSynchronousTransform();
    });
svick
  • 236,525
  • 50
  • 385
  • 514