I use the following method to do some tasks asynchronously and at the same time:
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 string in the array at the same time, then wait until all tasks are finished and return the result.
LoadDataAsync
twice for each item, once at the await ...
line, and once at the final .Result
property getter.await ...
line, Visual Studio warns me that the whole method would run in parallel, since there are not await
calls inside of the method.What am I doing wrong?
Are there better (shorter) ways to do the same?
Once again:
If I could teach people one thing about LINQ it would be that the value of a query is an object that executes the query, not the results of executing the query.
You create the query once, producing an object which can execute the query. You then execute the query twice. You have unfortunately created a query that not only computes a value but produces a side effect, and therefore, executing the query twice produces the side effect twice. Do not make reusable query objects that produce side effects, ever. Queries are a mechanism for asking questions, hence their name. They are not intended to be a control flow mechanism, but that's what you're using them for.
Executing the query twice produces two different results because of course the results of the query could have changed between the two executions. If the query is querying a database, say, the database could have changed between executions. If your query is "what are all the last names of every customer in London?" the answer could change from millisecond to millisecond, but the question stays the same. Always remember, the query represents a question.
I would be inclined to write something without queries. Use "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);
}
You have to remember that LINQ operations return queries, not the results of those queries. The variable results
doesn't represent the results of the operation that you have, but rather a query that is able to generate those results when iterated. You iterate it twice, executing the query on each of those occasions.
What you can do here is materialize the results of the query into a collection first, rather than storing 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);
A better way might be to format the async calls in such a way that the results of the tasks are returned by the await with their respective 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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With