web-dev-qa-db-fra.com

Est-il possible "d'attendre le retour de rendement DoSomethingAsync ()"

Les blocs d'itérateurs réguliers (c'est-à-dire "yield return") sont-ils incompatibles avec "async" et "wait"?

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 "impossible de charger la chaîne de message à partir des ressources".

Voici une autre tentative:

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

Mais encore une fois, le compilateur renvoie une erreur: "impossible de charger la chaîne de message à partir des ressources".


Voici le vrai code de programmation de mon projet

Ceci est très utile lorsque j'ai une tâche de liste, cette tâche peut être téléchargée HTML à partir d'une URL et j'utilise la syntaxe "rendement retour attendre la tâche", 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 pour toute aide.

93
jiangzhen

Ce que vous décrivez peut être accompli avec le Task.WhenAll méthode. Remarquez comment le code se transforme en une simple ligne. Ce qui se passe, c'est que chaque URL individuelle commence à télécharger, puis WhenAll est utilisé pour combiner ces opérations en un seul Task qui peut être attendu.

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

tl; dr Les itérateurs implémentés avec yield sont une construction bloquante, donc en ce moment, l'attente et le rendement sont incompatibles.

Long Parce que l'itération sur un IEnumerable est une opération de blocage, appeler une méthode marquée comme async l'exécutera toujours de manière bloquante, car elle doit attendre cela opération pour terminer.

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

L'attente Method mélange les significations. Voulez-vous attendre que Task ait un IEnumerable, puis bloquer son itération? Ou essayez-vous d'attendre chaque valeur de IEnumerable?

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

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

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

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

Bien sûr, foreach ne fonctionnera pas non plus avec cela et vous devrez répéter 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();
}
67
Arne Claassen

Selon les nouvelles fonctionnalités de C # 8.0 ( lien # 1 et lien # 2 ) nous aura IAsyncEnumerable<T> support d'interface qui permettra de mettre en œuvre votre deuxième tentative. Il 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);
}

On peut obtenir le même comportement en 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 qui ont été obtenus en parallèle. Le code ci-dessus implique que le code appelant obtiendra des résultats comme d'un flux un par un de manière asynchrone.

22
AlbertK

Je sais que je suis trop en retard avec la réponse, mais voici une autre solution simple qui peut être obtenue 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);
  });
}
17
Serge Semenov

Il y avait un plan à faire

https://github.com/dotnet/csharplang/issues/4

Mais actuellement ce n'est pas possible

4
Thaina

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

À partir de MSDN

Flux asynchrones

La fonctionnalité asynchrone/attente de C # 5.0 vous permet de consommer (et de produire) des résultats asynchrones dans du code simple, sans rappels:

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

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

Nous introduisons IAsyncEnumerable, qui est exactement ce à quoi vous vous attendez; une version asynchrone de IEnumerable. Le langage vous permet d'attendre que chacun d'eux consomme ses éléments et y retourne pour produire des éléments.

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

Le rendement ne fonctionne pas avec l'attente, malheureusement. Mais c'est à ça que sert Rx. Découvrez https://msdn.Microsoft.com/library/hh242985

1
Johan Franzén

Tout d'abord, gardez à l'esprit que le contenu Async n'est pas terminé. L'équipe C # a encore un long chemin à parcourir avant la sortie de C # 5.

Cela étant dit, je pense que vous voudrez peut-être rassembler les tâches qui sont lancées dans la fonction DownloadAllHtml d'une manière différente.

Par exemple, vous pouvez utiliser quelque chose comme ceci:

IEnumerable<Task<string>> DownloadAllUrl(string[] urls)
{
    foreach(var url in urls)
    {
        yield return DownloadHtmlAsync(url);
    }
}

async Task<string> DownloadHtmlAsync(url)
{
    // Do your downloading here...
}

Non pas que la fonction DownloadAllUrl soit [~ # ~] pas [~ # ~] un appel asynchrone. Mais, vous pouvez implémenter l'appel asynchrone sur une autre fonction (c'est-à-dire DownloadHtmlAsync).

La bibliothèque parallèle de tâches a le .ContinueWhenAny et .ContinueWhenAll fonctions.

Cela peut être utilisé comme ceci:

var tasks = DownloadAllUrl(...);
var tasksArray = tasks.ToArray();
var continuation = Task.Factory.ContinueWhenAll(tasksArray, completedTasks =>
{
    completedtask
});
continuation.RunSynchronously();
1
John Gietzen