【问题标题】:Task caching when performing Tasks in parallel with WhenAll与 WhenAll 并行执行任务时的任务缓存
【发布时间】:2014-08-26 13:28:04
【问题描述】:

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

// 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
}

【问题讨论】:

  • 能否贴出从缓存中读取的代码?
  • 添加了一些额外的上下文
  • 嘿,好问题!但我不确定你是如何在 wp8 中获得ConcurrentDictionary 的?

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


【解决方案1】:

您可以更改您的ConcurrentDictionary 以缓存Task&lt;Athlete&gt; 而不仅仅是Athlete。请记住,Task&lt;T&gt; 是一个承诺 - 最终将导致 T 的操作。因此,您可以缓存 操作 而不是 结果

ConcurrentDictionary<int, Task<Athlete>> cacheAthletes;

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

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

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

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

【讨论】:

  • 不错!稍后将尝试在我的开发电脑上进行测试!
  • 这可能会为同一个 ID 启动多个 LoadAthleteAsync 操作。工厂运行时字典不会锁定。它实现了一个 CAS 循环。
  • 那么@usr 有什么更好的解决方案的建议吗?所以我们不会触发多次加载?
  • @Depechie 例如使用CD&lt;Lazy&lt;Task&gt;&gt;
【解决方案2】:

您还需要跳过未成功完成的任务。 那是我的sn-p:

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

【讨论】:

    【解决方案3】:

    当缓存任务对象提供的值时,您需要确保缓存实现确保:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多