与WhenAll并行执行任务时的任务缓存

时间:2014-08-26 13:28:05

标签: c# windows-phone-8 winrt-async

所以我有这个小代码块,可以并行执行几个任务。

// no wrapping in Task, it is async
var activityList = await dataService.GetActivitiesAsync();

// Select a good enough tuple
var results = (from activity in activityList
               select new { 
                Activity = activity, 
                AthleteTask = dataService.GetAthleteAsync(activity.AthleteID)
               }).ToList(); // begin enumeration

// Wait for them to finish, ie relinquish control of the thread
await Task.WhenAll(results.Select(t => t.AthleteTask));

// Set the athletes
foreach(var pair in results)
{
  pair.Activity.Athlete = pair.AthleteTask.Result;
}

所以我正在为每个给定的活动下载运动员数据。但可能是我们要求同一位运动员多次。 我们怎样才能确保GetAthleteAsync方法只会在线获取实际数据(如果它还没有在我们的内存缓存中?)

目前我尝试在GetAthleteAsync方法中使用ConcurrentDictionary<int, Athelete>

private async Task<Athlete> GetAthleteAsync(int athleteID)
{
       if(cacheAthletes.Contains(athleteID))
             return cacheAthletes[atheleID];

       ** else fetch from web
}

3 个答案:

答案 0 :(得分:4)

您可以更改ConcurrentDictionary以缓存Task<Athlete>而不仅仅Athlete。请记住,Task<T>是一个承诺 - 最终会产生T的操作。因此,您可以缓存操作而不是结果

ConcurrentDictionary<int, Task<Athlete>> cacheAthletes;

然后,您的逻辑将如下所示:如果操作已经在缓存中,则立即(同步)返回缓存的任务。如果不是,则启动下载,将下载操作添加到缓存,然后返回新的下载操作。请注意所有&#34;下载操作&#34;逻辑被转移到另一个方法:

private Task<Athlete> GetAthleteAsync(int athleteID)
{
  return cacheAthletes.GetOrAdd(athleteID, id => LoadAthleteAsync(id));
}

private async Task<Athlete> LoadAthleteAsync(int athleteID)
{
  // Load from web
}

这样,对同一运动员的多个并行请求将获得相同的Task<Athlete>,并且每位运动员只下载一次。

答案 1 :(得分:1)

您还需要跳过未完成的任务。 这是我的片段:

ObjectCache _cache = MemoryCache.Default;
static object _lockObject = new object();
public Task<T> GetAsync<T>(string cacheKey, Func<Task<T>> func, TimeSpan? cacheExpiration = null) where T : class
{
    var task = (T)_cache[cacheKey];
    if (task != null) return task;          
    lock (_lockObject)
    {
        task = (T)_cache[cacheKey];
        if (task != null) return task;
        task = func();
        Set(cacheKey, task, cacheExpiration);
        task.ContinueWith(t => {
            if (t.Status != TaskStatus.RanToCompletion)
                _cache.Remove(cacheKey);
        });
    }
    return task;
}i

答案 2 :(得分:0)

当缓存Task-objects提供的值时,您要确保缓存实现确保:

  • 不会启动并行或不必要的操作来获取值。在您的情况下,这是关于为同一个ID避免多个GetAthleteAsync的问题。
  • 您不希望有负面缓存(即缓存失败的结果),或者如果您确实需要它,它需要是一个实现决策,您需要处理最终以某种方式替换失败的结果。
  • 缓存用户无法从缓存中获得无效结果,即使 await期间的值无效
  • 也是如此。

我有一个带有示例代码的blog post about caching Task-objects,可以确保上述所有点,并且可以在您的情况下使用。基本上我的解决方案是将Lazy<Task<T>>个对象存储在MemoryCache中。