Asynchronous tasks are evaluated twice

I use the following method to perform some tasks asynchronously and simultaneously:

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

I want the method to call LoadDataAsync for each row in the array at the same time, then wait for all tasks to complete and return the result.

  • If I run such a method, it calls LoadDataAsync twice for each element, once in the await ... line await ... and once in the final getter .Result .
  • If I delete the await ... line, Visual Studio warns me that the whole method will work in parallel, since there are no await calls inside the method.

What am I doing wrong?

Are there any better (shorter) ways to do the same?

+6
source share
4 answers

Again:

If I could teach people about LINQ, it would be that the value of the query is the object that executes the query, not the results of the query .

You create a request once, creating an object that can execute the request. Then you execute the request twice. Unfortunately, you created a query that not only calculates the value, but also produces a side effect, and therefore, executing the query twice causes a side effect twice. Do not use reusable request objects that create side effects, ever . Requests are a mechanism for raising questions, hence their name. They are not intended for the flow control mechanism, but for what you use them for.

Executing a query twice leads to two different results, because, of course, the query results can be changed between two executions. If the query queries the database, let's say the database could be changed between execution. If your request is "what are the names of all customers in London?" the answer may change from millisecond to millisecond, but the question remains the same. Always remember, the query is a question.

I would be inclined to write something without asking. Use the "foreach" loops to create side effects.

 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); } 
+23
source

You must remember that LINQ operations return queries, not the results of these queries. The results variable does not represent the results of the operation that you have, but rather a query that is able to generate these results when it is repeated. You repeat it twice, completing the query in each of these cases.

What you can do here is to first materialize the query results in the collection, and not store the query itself, in 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); 
+8
source

It is best to format asynchronous calls in such a way that the results of tasks are returned waiting with their corresponding keys:

 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
source

As a complement to Jesse Sweetland 's answer , the version is fully implemented:

 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: I suggested this as an edit, but was rejected due to too big changes.

0
source

Source: https://habr.com/ru/post/976066/


All Articles