138 votes

Est-il possible de "await yield return DoSomethingAsync()" ?

Les blocs itérateurs ordinaires (c'est-à-dire "yield return") sont-ils incompatibles avec "async" et "await" ?

Cela donne une bonne idée de ce que j'essaie de faire :

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    // I want to compose the single result to the final result, so I use the SelectMany
    var finalResult = UrlStrings.SelectMany(link =>   //i have an Urlstring Collection 
                   await UrlString.DownLoadHtmlAsync()  //download single result; DownLoadHtmlAsync method will Download the url's html code 
              );
     return finalResult;
}

Cependant, j'obtiens une erreur du compilateur citant "unable to load message string from resources".

Voici une autre tentative :

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
    foreach(var str in strs)
    {
        yield return await DoSomethingAsync( str)
    }
}

Mais là encore, le compilateur renvoie une erreur : "unable to load message string from resources".


Voici le code de programmation réel de mon projet

C'est très utile lorsque j'ai une liste de tâches, cette tâche peut être téléchargée en HTML à partir d'une URL. et que j'utilise la syntaxe "yield return await task", le résultat est que je veux IEnumerable<Foo> . Je ne veux pas écrire ce code :

async Task<IEnumerable<String>> DownLoadAllURL(String [] Strs)
{
    List<Foo> htmls= new ...
    foreach(var str in strs)
    {
        var html= await DownLoadHtmlAsync( str)
        htmls.Add(item)
    }
    return htmls;
}

Mais il semble que je doive le faire.

Merci de votre aide.

82voto

Brian Gideon Points 26683

Ce que vous décrivez peut être réalisé à l'aide de la fonction Task.WhenAll méthode. Remarquez que le code se transforme en une simple ligne. Ce qui se passe, c'est que chaque url individuelle commence à se télécharger, puis WhenAll est utilisé pour combiner ces opérations en une seule Task qui peut être attendue.

Task<IEnumerable<string>> DownLoadAllUrls(string[] urls)
{
    return Task.WhenAll(from url in urls select DownloadHtmlAsync(url));
}

79voto

Arne Claassen Points 2793

tl;dr Les itérateurs tels qu'ils sont implémentés avec yield sont une construction bloquante, donc pour l'instant await et yield sont incompatibles.

Longues Parce que l'itération sur un IEnumerable est une opération bloquante, l'appel d'une méthode marquée comme async l'exécutera quand même de manière bloquante, puisqu'il doit attendre que l'opération se termine.

async Task<IEnumerable<Foo>> Method(String [] Strs)
{
  foreach(var str in strs)
  {
    yield return await DoSomethingAsync( str)
  }
}  

L'attente Method mélange les sens. Voulez-vous attendre jusqu'à ce que la Task dispose d'un IEnumerable et bloquer ensuite l'itération sur celui-ci ? Ou essayez-vous d'attendre chaque valeur de l'élément IEnumerable ?

Je suppose que le second est le comportement souhaité et que, dans ce cas, la sémantique existante de l'itérateur ne fonctionnera pas. La sémantique IEnumerator<T> L'interface est essentiellement

public interface IEnumerator<T>
  T Current;
  bool MoveNext();
}

J'ignore Reset() car cela n'a pas de sens pour une séquence de résultats asynchrones. Mais ce dont vous auriez besoin, c'est de quelque chose comme ça :

public interface IAsyncEnumerator<T>
  T Current;
  Task<bool> MoveNext();
}

Bien sûr, foreach ne fonctionnera pas non plus et vous devrez itérer manuellement comme ceci :

var moveNext = await asyncEnumerator.MoveNext();
while(moveNext) {

  // get the value that was fetche asynchronously
  var v = asyncEnumerator.Current;

  // do something with that value

  // suspend current execution context until next value arrives or we are done
  moveNext = await asyncEnumerator.MoveNext();
}

69voto

Albert Points 2619

Selon les nouvelles fonctionnalités de C# 8.0 ( lien#1 y lien#2 ), nous aurons IAsyncEnumerable<T> qui vous permettra de mettre en œuvre votre deuxième tentative. Cela ressemblera à ceci :

async Task<Foo> DoSomethingAsync(string url)
{
    ...
}       
// producing IAsyncEnumerable<T>
async IAsyncEnumerable<Foo> DownLoadAllURL(string[] strs)
{
    foreach (string url in strs)
    {
        yield return await DoSomethingAsync(url);
    }
}
...
// using
await foreach (Foo foo in DownLoadAllURL(new string[] { "url1", "url2" }))
{
    Use(foo);
}

Nous pouvons obtenir le même comportement à C# 5 mais avec une sémantique différente :

async Task<Foo> DoSomethingAsync(string url)
{
    ...
}
IEnumerable<Task<Foo>> DownLoadAllURL(string[] strs)
{
    foreach (string url in strs)
    {
        yield return DoSomethingAsync(url);
    }
}

// using
foreach (Task<Foo> task in DownLoadAllURL(new string[] { "url1", "url2" }))
{
    Foo foo = await task;
    Use(foo);
}

La réponse de Brian Gideon implique que le code appelant obtiendra de manière asynchrone une collection de résultats obtenus en parallèle. Le code ci-dessus implique que le code appelant obtiendra des résultats comme ceux d'un flux un par un de manière asynchrone.

19voto

Serge Semenov Points 2621

Je sais que j'arrive trop tard pour répondre, mais voici une autre solution simple qui peut être réalisée avec cette bibliothèque :
GitHub : https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org : https://www.nuget.org/packages/AsyncEnumerator/
C'est beaucoup plus simple que Rx.

using System.Collections.Async;

static IAsyncEnumerable<string> ProduceItems(string[] urls)
{
  return new AsyncEnumerable<string>(async yield => {
    foreach (var url in urls) {
      var html = await UrlString.DownLoadHtmlAsync(url);
      await yield.ReturnAsync(html);
    }
  });
}

static async Task ConsumeItemsAsync(string[] urls)
{
  await ProduceItems(urls).ForEachAsync(async html => {
    await Console.Out.WriteLineAsync(html);
  });
}

6voto

Petter Pettersson Points 366

Cette fonctionnalité sera disponible à partir de C# 8.0. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

De MSDN

Flux asynchrones

La fonction async/await de C# 5.0 vous permet de consommer (et de produire) des résultats asynchrones dans un code simple, sans rappel :

async Task<int> GetBigResultAsync()
{
    var result = await GetResultAsync();
    if (result > 20) return result; 
    else return -1;
}

Ce n'est pas très utile si vous souhaitez consommer (ou produire) des flux continus de résultats, comme ceux que vous pourriez obtenir d'un appareil IoT ou d'un service cloud. Les flux asynchrones sont là pour ça.

Nous introduisons IAsyncEnumerable, qui est exactement ce que vous attendez : une version asynchrone de IEnumerable. Le langage vous permet d'attendre le foreach sur ces objets pour consommer leurs éléments, et de leur renvoyer le yield pour produire des éléments.

async IAsyncEnumerable<int> GetBigResultsAsync()
{
    await foreach (var result in GetResultsAsync())
    {
        if (result > 20) yield return result; 
    }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X