Ist es möglich, auf die Rendite-Rendite DoSomethingAsync () zu warten?


108

Sind reguläre Iteratorblöcke (dh "Yield Return") nicht mit "async" und "await" kompatibel?

Dies gibt eine gute Vorstellung davon, was ich versuche:

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

Ich erhalte jedoch einen Compilerfehler mit der Angabe "Nachrichtenzeichenfolge kann nicht aus Ressourcen geladen werden".

Hier ist ein weiterer Versuch:

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

Der Compiler gibt jedoch erneut den Fehler "Nachrichtenzeichenfolge kann nicht aus Ressourcen geladen werden" zurück.


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 Wait Task" verwende. Das Ergebnis ist das, was ich will 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.

Vielen Dank für jede Hilfe.


4
Diese Frage ist nicht besonders klar. Sie sollten genau erläutern, was Sie tun möchten. Es ist schwierig, allein anhand eines Codebeispiels zu bestimmen, was Sie tun möchten.
Justin

3
Das Ix_Experimental-Async NuGet-Paket enthält "asynchrone Enumeratoren" mit LINQ-Unterstützung. Sie verwenden den IAsyncEnumerator<T>von Arne definierten Typ.
Stephen Cleary

@StephenCleary, dass das Paket gelöscht wurde. Wissen Sie, ob dies in RX gerollt wurde, da dies vor Ewigkeiten war und von MS Rxteam verfasst wurde?
JoeBrockhaus

@ JoeBrockhaus: Sieht so aus, als würde es als Ix-Async weiterleben .
Stephen Cleary

Antworten:


72

Was Sie beschreiben, kann mit der Task.WhenAllMethode erreicht werden. Beachten Sie, wie sich der Code in einen einfachen Einzeiler verwandelt. Was passiert ist, dass jede einzelne URL mit dem Herunterladen beginnt und dann verwendet WhenAllwird, um diese Vorgänge zu einer einzigen zu kombinieren, auf die gewartet werden Taskkann.

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

10
async / await werden in diesem Fall nicht benötigt. Einfach asyncaus der Methode entfernen und return Task.WhenAlldirekt ausführen.
luiscubal

22
Die letzte Zeile kann prägnanter geschrieben werden alsurls.Select(DownloadHtmlAsync)
BlueRaja - Danny Pflughoeft

1
Dies stellte sich als genau das Problem heraus, mit dem ich konfrontiert war (langsames Herunterladen von Zeichenfolgen aus einer Reihe von URIs). Danke dir.
James Ko

13
Dies beantwortet nicht die Frage, ob Is it possible to await yield? Sie gerade eine Problemumgehung für diesen einen Anwendungsfall gefunden haben. Hat die allgemeine Frage nicht beantwortet.
Buh Buh

1
Ich würde gerne zurückkehren, Task<string[]>da dies darauf hinweisen würde, dass Sie keinen Iterator mit verzögerter Ausführung mehr zurückgeben, d. H. Alle URLs werden heruntergeladen.
Johnnycardy

73

tl; dr Iteratoren, wie sie mit Ausbeute implementiert sind, sind ein blockierendes Konstrukt, so dass ab sofort warten und Ausbeute nicht kompatibel sind.

Long Da das Iterieren über ein IEnumerableBlockierungsvorgang ist, wird der Aufruf einer als gekennzeichneten Methode asyncweiterhin blockierend ausgeführt, da auf den Abschluss dieses Vorgangs gewartet werden muss.

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

Das Warten Methodmischt Bedeutungen. Möchten Sie warten, bis das ein Taskhat, IEnumerableund dann blockieren, um darüber zu iterieren? Oder versuchen Sie, jeden Wert von zu erwarten IEnumerable?

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

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

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

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

Funktioniert natürlich foreachauch nicht damit und Sie müssten manuell wie folgt 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();
}

1
Halten Sie es für möglich, dass die nächste C # -Sprachenversion foreachIhre Hypothese unterstützt IAsyncEnumerator<T>?
Dai

1
@Dai Ich denke, es ist sehr in der Pipeline. für C # 8.0 / Hatte darüber gelesen.
Nawfal


40

Entsprechend den neuen Funktionen in C # 8.0 ( Link Nr. 1 und Link Nr. 2 ) IAsyncEnumerable<T>bieten wir Schnittstellenunterstützung, mit der Sie Ihren zweiten Versuch implementieren 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 das gleiche Verhalten bei C # 5 erreichen, aber 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);
}

Die Antwort von Brian Gideon 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 von einem Stream nacheinander auf asynchrone Weise erhält.


17

Ich weiß, dass ich mit der Antwort zu spät bin, aber hier ist eine weitere 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);
  });
}

5

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 erzeugen):

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

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

Wir führen IAsyncEnumerable ein, genau das, was Sie erwarten würden. eine asynchrone Version von IEnumerable. Mit der Sprache können Sie darauf warten, dass diese über ihre Elemente verbraucht werden, und sie zurückgeben, um Elemente zu produzieren.

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


1

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 DownloadAllHtmlFunktion ausgelöst werden, auf andere Weise erfassen 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 DownloadAllUrlFunktion KEIN asynchroner Aufruf ist. Sie können den asynchronen Aufruf jedoch auch für eine andere Funktion (dh DownloadHtmlAsync) implementieren lassen .

Die Task Parallel Library verfügt über die Funktionen .ContinueWhenAnyund .ContinueWhenAll.

Das kann so verwendet werden:

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

Sie können auch einen 'Header' (vor der Schleife) haben, wenn Sie Ihre Methode als asynchron definieren Task<IEnumerable<Task<string>>> DownloadAllUrl. Oder wenn Sie Fußzeilenaktionen möchten IEnumerable<Task>. ZB gist.github.com/1184435
Jonathan Dickinson

1
Nun , da das asyncMaterial ist fertig und C # 5.0 ist freigegeben, dies kann aktualisiert werden.
casperOne

Ich mag dieses, aber wie in diesem Fall foreach (var url in await GetUrls ()) {ield return GetContent (url)}; Ich habe nur einen Weg gefunden, mich in zwei Methoden aufzuteilen.
Juan Pablo Garcia Coello


-1

Diese Lösung funktioniert wie erwartet. Beachten Sie den await Task.Run(() => enumerator.MoveNext())Teil.

using (var enumerator = myEnumerable.GetEnumerator())
{
    while (true)
    {
        if (enumerator.Current != null)
        {
            //TODO: do something with enumerator.Current
        }

        var enumeratorClone = monitorsEnumerator;
        var hasNext = await Task.Run(() => enumeratorClone.MoveNext());
        if (!hasNext)
        {
            break;
        }
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.