异步任务被评估两次

che*_*ica 8 .net c# 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

我究竟做错了什么?

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

Eri*_*ert 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);
}
Run Code Online (Sandbox Code Playgroud)


Ser*_*rvy 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);
Run Code Online (Sandbox Code Playgroud)