异步任务被评估两次

时间:2014-09-30 15:33:19

标签: c# .net async-await

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

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,然后等待所有任务完成并返回结果。

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

我做错了什么?

有更好(更短)的方法吗?

4 个答案:

答案 0 :(得分:26)

再次:

如果我可以向人们传授关于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);
}

答案 1 :(得分: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);

答案 2 :(得分:3)

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

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);
}

答案 3 :(得分: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:我建议将其作为编辑,但由于更改太多而被拒绝。