Cold Tasks和TaskExtensions.Unwrap

Fow*_*owl 4 c# task-parallel-library async-await c#-5.0 .net-4.5

我有一个使用冷(未启动)任务的缓存类,以避免多次运行昂贵的东西.

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这很有效,直到您的任务实际使用C#5 await,ala实现

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

现在它看起来像TaskExtensions.Unwrap()我需要Task<Task<T>>变成一个Task<T>,但似乎它返回的包装实际上不支持Start()- 它抛出一个异常.

TaskCompletionSource (我去寻找稍微特殊的任务需求)似乎没有任何设施用于此类事情.

是否有替代方案TaskExtensions.Unwrap()支持"冷任务"?

svi*_*ick 6

你需要做的就是Task在打开它之前保持它并开始:

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;
}
Run Code Online (Sandbox Code Playgroud)

用法:

cache.GetOrAddAsync(
    "key", async () =>
    {
        var r = await AsyncOperation();
        return r.FastSynchronousTransform();
    });
Run Code Online (Sandbox Code Playgroud)