2014-09-30 51 views
6

我用下面的方法来异步执行一些任务,并在同一时间:异步任务是两次评估

public async Task<Dictionary<string, object>> Read(string[] queries) 
{ 
    var results = queries.Select(query => new Tuple<string, Task<object>>(query, LoadDataAsync(query))); 

    await Task.WhenAll(results.Select(x => x.Item2).ToArray()); 

    return results 
     .ToDictionary(x => x.Item1, x => x.Item2.Result); 
} 

我想要的方法来调用LoadDataAsync用于在同一时间阵列中的每个字符串,然后等到所有任务完成并返回结果。

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

我在做什么错?

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

+3

嗯,当然他们是 - 你在枚举结果查询两次。如果首先使用'queries ... ToArray()',它将起作用。你枚举两次'queries',每次都做一个新的'LoadDataAsync'。 – Luaan 2014-09-30 15:38:16

+1

我对你的代码示例有些困惑。数组'items'被忽略,并使用一个叫做'queries'的神秘全局变量。你发布了一个不完整的代码示例吗? – 2014-09-30 15:46:48

+0

是的,对不起。更正它。不应该在SO内进行代码编辑。 – cheeesus 2014-09-30 17:16:01

回答

23

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

您创建查询一次,生成一个可以执行查询的对象。然后执行两次查询。您不幸地创建了一个查询,该查询不仅计算值,而且还产生副作用,因此执行查询两次会产生副作用两次。 不要使产生副作用的可重复使用的查询对象永远为。查询是询问问题的机制,因此它们的名称。它们并不打算成为一种控制流程机制,但这正是您使用它们的原因。

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

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

public async Task<Dictionary<string, object>> Read(IEnumerable<string> queries) 
{ 
    var tasks = new Dictionary<string, Task<object>>(); 
    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); 
} 
3

一个更好的办法可能是格式化这样的方式的任务的结果是由等待着他们各自的键返回的异步调用:再次

public async Task<KeyValuePair<string, object>> LoadNamedResultAsync(string query) 
{ 
    object result = null; 
    // Async query setting result 
    return new KeyValuePair<string, object>(query, result) 
} 

public async Task<IDictionary<string, object>> Read(string[] queries) 
{ 
    var tasks = queries.Select(LoadNamedResultAsync); 
    var results = await Task.WhenAll(tasks); 
    return results.ToDictionary(r => r.Key, r => r.Value); 
} 
8

你必须记住,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); 
0

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

public async Task<KeyValuePair<string, object>> LoadNamedResultAsync(string query) 
{ 
    Task<object> getLoadDataTask = await LoadDataAsync(query); 
    return new KeyValuePair<string, object>(query, getLoadDataTask.Result); 
} 

public async Task<IDictionary<string, object>> Read(string[] queries) 
{ 
    var tasks = queries.Select(LoadNamedResultAsync); 
    var results = await Task.WhenAll(tasks); 
    return results.ToDictionary(r => r.Key, r => r.Value); 
} 

REM:我提出这个作为编辑,但正是因为太多的变化拒绝。