web-dev-qa-db-de.com

Ist es möglich, auf "yield return DoSomethingAsync ()" zu warten?

Sind reguläre Iteratorblöcke (d. H. "Yield return") mit "async" und "await" nicht kompatibel?

Dies gibt eine gute Vorstellung davon, was ich versuche zu tun:

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;
}

Es wird jedoch ein Compiler-Fehler angezeigt, der angibt, dass die Nachrichtenzeichenfolge nicht aus Ressourcen geladen werden kann.

Hier ist ein weiterer Versuch:

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

Aber auch hier gibt der Compiler einen Fehler zurück: "Nachrichtenzeichenfolge kann nicht aus Ressourcen geladen werden".


Hier ist der eigentliche Programmcode in meinem Projekt

Dies ist sehr nützlich, wenn ich eine Listenaufgabe habe, diese Aufgabe HTML von einer URL herunterladen kann und ich die Syntax "yield return await task" verwende. Das Ergebnis ist Ich möchte IEnumerable<Foo>. Ich möchte diesen Code nicht schreiben:

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;
}

Aber es scheint, dass ich muss.

Danke für jede Hilfe.

93
jiangzhen

Was Sie beschreiben, können Sie mit dem Task.WhenAll Methode. Beachten Sie, wie sich der Code in einen einfachen Einzeiler verwandelt. Was passiert ist, dass jede einzelne URL mit dem Herunterladen beginnt und dann WhenAll verwendet wird, um diese Operationen zu einer einzigen Task zu kombinieren, auf die gewartet werden kann.

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

tl; dr Iteratoren, wie sie mit yield implementiert wurden, sind ein blockierendes Konstrukt, daher warten Sie ab und yield ist inkompatibel.

Long Da das Iterieren über ein IEnumerable eine blockierende Operation ist, führt das Aufrufen einer als async markierten Methode diese immer noch blockierend aus, da sie darauf warten muss Operation zu beenden.

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

Das wartende Method vermischt Bedeutungen. Möchten Sie warten, bis das Task ein IEnumerable hat, und dann das Iterieren darüber blockieren? Oder versuchen Sie, auf jeden Wert von IEnumerable zu warten?

Ich gehe davon aus, dass das zweite das gewünschte Verhalten ist und in diesem Fall die vorhandene Iterator-Semantik nicht funktioniert. Die Schnittstelle IEnumerator<T> Ist im Grunde genommen

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

Ich ignoriere Reset(), da es für eine Folge von asynchronen Ergebnissen keinen Sinn ergibt. Aber was Sie brauchen würden, ist so etwas:

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

Natürlich funktioniert auch foreach nicht und Sie müssten manuell so iterieren:

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

Nach den neuen Funktionen bei C # 8.0 ( link # 1 und link # 2 ) haben wir 'Ich möchte gerne IAsyncEnumerable<T> Schnittstellenunterstützung, mit der Sie Ihren zweiten Versuch durchführen können. Es wird so aussehen:

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);
}

Wir können dasselbe Verhalten bei C # 5 erreichen, jedoch mit einer anderen Semantik:

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);
}

Brian Gideons Antwort impliziert, dass der aufrufende Code asynchron eine Sammlung von Ergebnissen erhält, die parallel erhalten wurden. Der obige Code impliziert, dass der aufrufende Code Ergebnisse wie aus einem Stream eins nach dem anderen auf asynchrone Weise abruft.

22
AlbertK

Ich weiß, dass ich mit der Antwort zu spät bin, aber hier ist eine andere einfache Lösung, die mit dieser Bibliothek erreicht werden kann:
GitHub: https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org: https://www.nuget.org/packages/AsyncEnumerator/
Es ist viel einfacher als 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

Es gab einen Plan zu tun

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

Aber derzeit ist es nicht möglich

4
Thaina

Diese Funktion ist ab C # 8.0 verfügbar. https://blogs.msdn.Microsoft.com/dotnet/2018/11/12/building-c-8-0/

Von MSDN

Asynchrone Streams

Mit der Async/Warten-Funktion von C # 5.0 können Sie asynchrone Ergebnisse in einfachem Code ohne Rückrufe verarbeiten (und produzieren):

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

Es ist nicht so hilfreich, wenn Sie kontinuierliche Datenströme verwenden (oder produzieren) möchten, wie sie beispielsweise von einem IoT-Gerät oder einem Cloud-Dienst stammen. Dafür gibt es asynchrone Streams.

Wir führen IAsyncEnumerable ein, genau das, was Sie erwarten. eine asynchrone Version von IEnumerable. Mit der Sprache können Sie darauf warten, dass sie ihre Elemente verzehren und zu ihnen zurückkehren, um Elemente zu produzieren.

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

Ertrag funktioniert leider nicht mit abwarten. Aber dafür ist Rx da. Check out https://msdn.Microsoft.com/library/hh242985

1
Johan Franzén

Denken Sie zunächst daran, dass das Async-Zeug noch nicht fertig ist. Das C # -Team hat noch einen langen Weg vor sich, bevor C # 5 veröffentlicht wird.

Abgesehen davon denke ich, dass Sie die Aufgaben, die in der Funktion DownloadAllHtml ausgelöst werden, auf andere Weise sammeln möchten.

Sie können beispielsweise Folgendes verwenden:

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

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

Nicht, dass die DownloadAllUrl -Funktion [~ # ~] nicht [~ # ~] ein asynchroner Aufruf ist. Sie können den asynchronen Aufruf jedoch auch für eine andere Funktion implementieren lassen (d. H. DownloadHtmlAsync).

Die Task Parallel Library hat die .ContinueWhenAny und .ContinueWhenAll Funktionen.

Das kann so verwendet werden:

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