异步任务被评估两次

我使用以下方法异步并同时执行某些任务:

public async Task<Dictionary> Read(string[] queries) { var results = queries.Select(query => new Tuple<string, Task>(query, LoadDataAsync(query))); await Task.WhenAll(results.Select(x => x.Item2).ToArray()); return results .ToDictionary(x => x.Item1, x => x.Item2.Result); } 

我希望该方法同时为数组中的每个字符串调用LoadDataAsync ,然后等待所有任务完成并返回结果。

  • 如果我运行这样的方法,它会为每个项调用两次LoadDataAsync ,一次在await ...行,一次在最终的.Result属性getter。
  • 如果我删除await ...行,Visual Studio会警告我整个方法将并行运行,因为在方法内部没有await调用。

我究竟做错了什么?

是否有更好(更短)的方法来做同样的事情?

再来一次:

如果我可以教人们关于LINQ的一件事,那就是查询的值是执行查询的对象,而不是执行查询的结果

您创建一次查询,生成一个可以执行查询的对象。 然后,您执行两次查询。 遗憾的是,您创建了一个不仅计算值而且产生副作用的查询,因此,执行两次查询会产生两次副作用。 不要制作产生副作用的可重用查询对象 。 查询是一种提问的机制,因此也就是他们的名字。 它们并非旨在成为一种控制流机制,而是您正在使用它们。

执行两次查询会产生两个不同的结果,因为查询的结果当然可以在两次执行之间发生变化 。 如果查询正在查询数据库,那么数据库可能在执行之间发生了变化。 如果您的查询是“伦敦每位客户的姓氏是什么?” 答案可以从毫秒变为毫秒,但问题保持不变。 永远记住,查询代表一个问题

我会倾向于写一些没有疑问的东西。 使用“foreach”循环创建副作用。

 public async Task> Read(IEnumerable queries) { var tasks = new Dictionary>(); foreach (string query in queries) tasks.Add(query, LoadDataAsync(query)); await Task.WhenAll(tasks.Values); return tasks.ToDictionary(x => x.Key, x => x.Value.Result); } 

您必须记住LINQ操作返回查询,而不是这些查询的结果。 变量results不代表您拥有的操作的结果,而是表示在迭代时能够生成这些结果的查询 。 您迭代它两次,在每个场合执行查询。

您可以在此处执行的操作是首先将查询结果实现到集合中,而不是将查询本身存储在results

 var results = queries.Select(query => Tuple.Create(query, LoadDataAsync(query))) .ToList(); await Task.WhenAll(results.Select(x => x.Item2)); return results .ToDictionary(x => x.Item1, x => x.Item2.Result); 

更好的方法可能是格式化异步调用,以便await使用各自的键返回任务的结果:

 public async Task> LoadNamedResultAsync(string query) { object result = null; // Async query setting result return new KeyValuePair(query, result) } public async Task> Read(string[] queries) { var tasks = queries.Select(LoadNamedResultAsync); var results = await Task.WhenAll(tasks); return results.ToDictionary(r => r.Key, r => r.Value); } 

作为Jesse Sweetland答案的补充,完全实现的版本:

 public async Task> LoadNamedResultAsync(string query) { Task getLoadDataTask = await LoadDataAsync(query); return new KeyValuePair(query, getLoadDataTask.Result); } public async Task> Read(string[] queries) { var tasks = queries.Select(LoadNamedResultAsync); var results = await Task.WhenAll(tasks); return results.ToDictionary(r => r.Key, r => r.Value); } 

Rem:我建议将其作为编辑,但由于更改太多而被拒绝。