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

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

// 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 Athelete ConcurrentDictionary

 private async Task GetAthleteAsync(int athleteID) { if(cacheAthletes.Contains(athleteID)) return cacheAthletes[atheleID]; ** else fetch from web } 

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

 ConcurrentDictionary> cacheAthletes; 

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

 private Task GetAthleteAsync(int athleteID) { return cacheAthletes.GetOrAdd(athleteID, id => LoadAthleteAsync(id)); } private async Task LoadAthleteAsync(int athleteID) { // Load from web } 

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

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

 ObjectCache _cache = MemoryCache.Default; static object _lockObject = new object(); public Task GetAsync(string cacheKey, Func> 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 

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

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

我有一篇关于使用示例代码缓存任务对象的博客文章 ,该文章确保了上述所有要点,并且可能对您的情况有用。 基本上我的解决方案是将Lazy>对象存储在MemoryCache中。