Les tâches asynchrones sont évaluées deux fois

J’utilise la méthode suivante pour effectuer certaines tâches de manière asynchrone et simultanée:

public async Task<Dictionary> Read(ssortingng[] queries) { var results = queries.Select(query => new Tuple<string, Task>(query, LoadDataAsync(query))); await Task.WhenAll(results.Select(x => x.Item2).ToArray()); return results .ToDictionary(x => x.Item1, x => x.Item2.Result); } 

Je veux que la méthode appelle LoadDataAsync pour chaque chaîne du tableau en même temps, puis attende que toutes les tâches soient terminées et renvoie le résultat.

  • Si LoadDataAsync cette méthode, elle appelle LoadDataAsync deux fois pour chaque élément, une fois à la ligne .Result await ... et une fois au getter final de la propriété .Result .
  • Si je supprime la ligne await ... , Visual Studio me prévient que toute la méthode est exécutée en parallèle, car il n’y a pas d’appels en await intérieur de la méthode.

Qu’est-ce que je fais mal?

Y a-t-il de meilleures façons (plus courtes) de faire la même chose?

Encore une fois:

Si je pouvais enseigner quelque chose aux gens sur LINQ, ce serait que la valeur d’une requête est un object qui exécute la requête, pas les résultats de l’exécution de la requête .

Vous créez la requête une fois, produisant un object capable d’exécuter la requête. Vous exécutez ensuite la requête deux fois. Vous avez malheureusement créé une requête qui non seulement calcule une valeur, mais produit également un effet secondaire. Par conséquent, l’exécution de la requête deux fois produit l’effet secondaire deux fois. Ne créez jamais d’objects de requête réutilisables produisant des effets secondaires . Les requêtes sont un mécanisme pour poser des questions , d’où leur nom. Ils ne sont pas destinés à être un mécanisme de stream de contrôle, mais c’est pour cela que vous les utilisez.

L’exécution de la requête à deux resockets produit deux résultats différents car, bien entendu, les résultats de la requête pourraient avoir changé entre les deux exécutions . Par exemple, si la requête interroge une firebase database, celle-ci peut avoir changé entre les exécutions. Si votre requête est “quels sont tous les noms de chaque client à Londres?” la réponse pourrait passer d’une milliseconde à une milliseconde, mais la question rest la même. Rappelez-vous toujours, la requête représente une question .

Je serais enclin à écrire quelque chose sans questions. Utilisez des boucles “foreach” pour créer des effets secondaires.

 public async Task> Read(IEnumerable queries) { var tasks = new Dictionary>(); foreach (ssortingng query in queries) tasks.Add(query, LoadDataAsync(query)); await Task.WhenAll(tasks.Values); return tasks.ToDictionary(x => x.Key, x => x.Value.Result); } 

N’oubliez pas que les opérations LINQ renvoient des requêtes, pas les résultats de ces requêtes. Les results variable ne représentent pas les résultats de l’opération que vous avez, mais plutôt une requête capable de générer ces résultats lors de l’itération . Vous le parcourez deux fois, en exécutant la requête à chacune de ces occasions.

Ce que vous pouvez faire ici est de matérialiser d’abord les résultats de la requête dans une collection, plutôt que de stocker la requête elle-même dans les 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); 

Un meilleur moyen pourrait être de formater les appels asynchrones de manière à ce que les résultats des tâches soient renvoyés par wait dans leurs clés respectives:

 public async Task> LoadNamedResultAsync(ssortingng query) { object result = null; // Async query setting result return new KeyValuePair(query, result) } public async Task> Read(ssortingng[] queries) { var tasks = queries.Select(LoadNamedResultAsync); var results = await Task.WhenAll(tasks); return results.ToDictionary(r => r.Key, r => r.Value); } 

En complément de la réponse de Jesse Sweetland , la version entièrement implémentée:

 public async Task> LoadNamedResultAsync(ssortingng query) { Task getLoadDataTask = await LoadDataAsync(query); return new KeyValuePair(query, getLoadDataTask.Result); } public async Task> Read(ssortingng[] queries) { var tasks = queries.Select(LoadNamedResultAsync); var results = await Task.WhenAll(tasks); return results.ToDictionary(r => r.Key, r => r.Value); } 

Rem: J’ai proposé ceci comme édition mais cela a été rejeté à cause de trop de changements.