web-dev-qa-db-fra.com

Appel de la méthode asynchrone dans IEnumerable.Select

J'ai le code suivant, convertissant des éléments entre les types R et L à l'aide d'une méthode asynchrone:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = new List<L>();
        foreach (var remoteItem  in remoteItems )
        {
            mappedItems.Add(await MapToLocalObject(remoteItem));
        }

        //Do stuff with mapped items

        ...
    }

    private async Task<L> MapToLocalObject(R remoteObject);
}

Est-il possible d'écrire à l'aide d'un appel IEnumerable.Select (ou similaire) pour réduire les lignes de code? J'ai essayé ceci:

class MyClass<R,L> {

    public async Task<bool> MyMethodAsync(List<R> remoteItems) {
        ...

        List<L> mappedItems = remoteItems.Select<R, L>(async r => await MapToLocalObject(r)).ToList<L>();

        //Do stuff with mapped items

        ...
    }
}

Mais je reçois une erreur:

"Impossible de convertir l'expression lambda asynchrone en délégué de type 'System.Func<R,int,L>'. Une expression lambda asynchrone peut renvoyer void, Task ou Task<T>, dont aucun n'est convertible en 'System.Func<R,int,L>'. "

Je crois que je manque quelque chose sur les mots-clés asynchrones/attendre, mais je ne peux pas comprendre quoi. Est-ce qu'un organisme sait comment modifier mon code pour le faire fonctionner?

37
PKeno

Vous pouvez résoudre ce problème en considérant les types en jeu. Par exemple, MapToLocalObject - lorsqu'il est considéré comme une fonction asynchrone - est mappé de R à L. Mais si vous la voyez comme une fonction synchrone, elle est mappée de R à Task<L>.

Task est un "futur", donc Task<L> peut être considéré comme un type qui produira un L à un moment donné dans le futur.

Vous pouvez donc facilement convertir une séquence de R en une séquence de Task<L>:

IEnumerable<Task<L>> mappingTasks = remoteItems.Select(remoteItem => MapToLocalObject(remoteItem));

Notez qu'il existe une différence sémantique importante entre celui-ci et votre code d'origine. Votre code d'origine attend que chaque objet soit mappé avant de passer à l'objet suivant; ce code démarrera tous les mappages simultanément.

Votre résultat est une séquence de tâches - une séquence de futurs résultats L. Pour travailler avec des séquences de tâches, il existe quelques opérations courantes. Task.WhenAll et Task.WhenAny sont des opérations intégrées pour les exigences les plus courantes. Si vous voulez attendre que tous les mappages soient terminés, vous pouvez faire:

L[] mappedItems = await Task.WhenAll(mappingTasks);

Si vous préférez gérer chaque élément à la fin, vous pouvez utiliser OrderByCompletion de ma bibliothèque AsyncEx :

Task<L>[] orderedMappingTasks = mappingTasks.OrderByCompletion();
foreach (var task in orderedMappingTasks)
{
  var mappedItem = await task;
  ...
}
69
Stephen Cleary