Czy można „czekać na zwrot wyniku DoSomethingAsync ()”


108

Czy zwykłe bloki iteratora (np. „Yield return”) są niezgodne z „async” i „await”?

To daje dobre wyobrażenie o tym, co próbuję zrobić:

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

Jednak pojawia się błąd kompilatora, powołujący się na „nie można załadować ciągu wiadomości z zasobów”.

Oto kolejna próba:

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

Ale znowu kompilator zwraca błąd: „nie można załadować ciągu komunikatów z zasobów”.


Oto prawdziwy kod programistyczny w moim projekcie

Jest to bardzo przydatne, gdy mam zadanie listy, to zadanie można pobrać HTML z adresu URL i używam składni „yield return await task”, czego oczekuję IEnumerable<Foo>. Nie chcę pisać tego kodu:

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

Ale wydaje się, że muszę.

Dzięki za wszelką pomoc.


4
To pytanie nie jest szczególnie jasne, powinieneś szczegółowo opisać, co dokładnie chcesz zrobić - trudno jest określić, co chcesz zrobić, na podstawie samej próbki kodu.
— Justin,

3
Pakiet Ix_Experimental-Async NuGet zawiera „asynchroniczne moduły wyliczające” wraz z obsługą LINQ. Używają IAsyncEnumerator<T>typu zdefiniowanego przez Arne'a.
— Stephen Cleary

@StephenCleary ten pakiet został usunięty z listy. Ponieważ to było wieki temu i napisane przez rxteam MS, czy wiesz, czy to zostało wprowadzone do RX?
— JoeBrockhaus

@JoeBrockhaus: Wygląda na to, że żyje dalej jako Ix-Async .
— Stephen Cleary

Odpowiedzi:


72

To, co opisujesz, można osiągnąć za pomocą tej Task.WhenAllmetody. Zwróć uwagę, jak kod zamienia się w prostą jednowierszową. Dzieje się tak, że każdy indywidualny adres URL zaczyna się pobierać, a następnie WhenAlljest używany do łączenia tych operacji w jeden, na Taskktóry można czekać.

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

10
async / await nie są potrzebne w tym przypadku. Po prostu usuń asyncz metody i zrób return Task.WhenAllbezpośrednio.
— luiscubal

22
Ostatnią linijkę można napisać bardziej zwięźle, ponieważurls.Select(DownloadHtmlAsync)
— BlueRaja - Danny Pflughoeft

1
Okazało się, że był to dokładnie ten problem, z którym miałem do czynienia (leniwe pobieranie ciągów z serii identyfikatorów URI). Dziękuję Ci.
— James Ko,

13
To właściwie nie odpowiada na pytanie, Is it possible to await yield? że właśnie znalazłeś obejście tego jednego przypadku użycia. Nie odpowiedział na ogólne pytanie.
— Buh Buh

1
Byłbym skłonny do powrotu, Task<string[]>ponieważ oznaczałoby to, że nie zwracasz już iteratora z odroczonym wykonaniem, tj. wszystkie adresy URL są pobierane.
— johnnycardy

73

tl; dr Iteratory zaimplementowane z wydajnością są konstrukcją blokującą, więc w tej chwili oczekiwanie i wydajność są niekompatybilne.

Long Ponieważ iteracja po operacji IEnumerablejest operacją blokującą, wywołanie metody oznaczonej jako asyncnadal będzie ją wykonywać w sposób blokujący, ponieważ musi czekać na zakończenie tej operacji.

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

Oczekiwanie Methodmiesza znaczenia. Czy chcesz poczekać, aż Taskpojawi IEnumerablesię ikona, a następnie zablokować jej iterację? A może próbujesz poczekać na każdą wartość IEnumerable?

Zakładam, że drugim jest pożądane zachowanie iw takim przypadku istniejąca semantyka Iteratora nie będzie działać. IEnumerator<T>Interfejs jest w zasadzie

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

Ignoruję, Reset()ponieważ nie ma to sensu w przypadku sekwencji wyników asynchronicznych. Ale to, czego potrzebujesz, to coś takiego:

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

Oczywiście, foreachteż z tym nie zadziała i będziesz musiał wykonać iterację ręcznie w ten sposób:

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
Czy uważasz, że jest możliwe, że następna wersja językowa C # doda foreachobsługę Twojej hipotetycznej IAsyncEnumerator<T>?
— Dai

1
@Dai Myślę, że jest w przygotowaniu. dla C # 8.0 / Czytałem o tym.
— nawfal


40

Zgodnie z nowymi funkcjami w C # 8.0 ( łącze nr 1 i łącze nr 2 ) będziemy mieć IAsyncEnumerable<T>obsługę interfejsu, która umożliwi zaimplementowanie drugiej próby. Będzie to wyglądać tak:

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

Możemy osiągnąć to samo zachowanie w C # 5, ale z inną semantyką:

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

Odpowiedź Briana Gideona sugeruje, że wywołujący kod otrzyma asynchronicznie zbiór wyników uzyskanych równolegle. Powyższy kod oznacza, że ​​kod wywołujący będzie uzyskiwał wyniki, takie jak ze strumienia jeden po drugim w sposób asynchroniczny.


17

Wiem, że jestem za późno z odpowiedzią, ale oto kolejne proste rozwiązanie, które można osiągnąć dzięki tej bibliotece:
GitHub: https://github.com/tyrotoxin/AsyncEnumerable
NuGet.org: https: //www.nuget .org / packages / AsyncEnumerator /
To znacznie prostsze niż 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

Ta funkcja będzie dostępna od C # 8.0. https://blogs.msdn.microsoft.com/dotnet/2018/11/12/building-c-8-0/

Z MSDN

Strumienie asynchroniczne

Funkcja async / await w C # 5.0 umożliwia konsumowanie (i tworzenie) wyników asynchronicznych w prostym kodzie, bez wywołań zwrotnych:

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

Nie jest to tak pomocne, jeśli chcesz konsumować (lub wytwarzać) ciągłe strumienie wyników, takie jak można uzyskać z urządzenia IoT lub usługi w chmurze. Do tego służą strumienie asynchroniczne.

Przedstawiamy IAsyncEnumerable, czyli dokładnie to, czego można się spodziewać; asynchroniczna wersja IEnumerable. Język pozwala ci czekać na wszystko, aż konsumują ich elementy i zwracają je do produkcji elementów.

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


1

Przede wszystkim pamiętaj, że rzeczy Async nie są skończone. Zespół języka C # wciąż ma przed sobą długą drogę do wydania języka C # 5.

Biorąc to pod uwagę, myślę, że możesz chcieć zebrać zadania, które są zwolnione w DownloadAllHtmlfunkcji w inny sposób.

Na przykład możesz użyć czegoś takiego:

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

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

Nie oznacza to, że DownloadAllUrlfunkcja NIE jest wywołaniem asynchronicznym. Ale możesz mieć wywołanie asynchroniczne zaimplementowane w innej funkcji (tj DownloadHtmlAsync.).

Biblioteka równoległa zadań ma funkcje .ContinueWhenAnyi .ContinueWhenAll.

Można tego użyć w następujący sposób:

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

Możesz również mieć „nagłówek” (przed pętlą), jeśli zdefiniujesz swoją metodę jako asynchroniczną Task<IEnumerable<Task<string>>> DownloadAllUrl. Lub, jeśli chcesz, aby akcje „stopki” IEnumerable<Task>. Np. Gist.github.com/1184435
— Jonathan Dickinson

1
Teraz, gdy asyncwszystko jest skończone i C # 5.0 jest wydany, można to zaktualizować.
— casper 1

Podoba mi się ten, ale jak w tym przypadku foreach (var url in await GetUrls ()) {yield return GetContent (url)}; Znalazłem tylko sposób na podział na dwie metody.
— Juan Pablo Garcia Coello


-1

To rozwiązanie działa zgodnie z oczekiwaniami. Zwróć uwagę na await Task.Run(() => enumerator.MoveNext())część.

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;
        }
    }
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.