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.
c#
async-await
c#-5.0
iasyncenumerable
jiangzhen
źródło
źródło
IAsyncEnumerator<T>
typu zdefiniowanego przez Arne'a.Odpowiedzi:
To, co opisujesz, można osiągnąć za pomocą tej
Task.WhenAll
metody. Zwróć uwagę, jak kod zamienia się w prostą jednowierszową. Dzieje się tak, że każdy indywidualny adres URL zaczyna się pobierać, a następnieWhenAll
jest używany do łączenia tych operacji w jeden, naTask
który można czekać.źródło
async
z metody i zróbreturn Task.WhenAll
bezpośrednio.urls.Select(DownloadHtmlAsync)
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.Task<string[]>
ponieważ oznaczałoby to, że nie zwracasz już iteratora z odroczonym wykonaniem, tj. wszystkie adresy URL są pobierane.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
IEnumerable
jest operacją blokującą, wywołanie metody oznaczonej jakoasync
nadal będzie ją wykonywać w sposób blokujący, ponieważ musi czekać na zakończenie tej operacji.Oczekiwanie
Method
miesza znaczenia. Czy chcesz poczekać, ażTask
pojawiIEnumerable
się 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 zasadzieIgnoruję,
Reset()
ponieważ nie ma to sensu w przypadku sekwencji wyników asynchronicznych. Ale to, czego potrzebujesz, to coś takiego:Oczywiście,
foreach
też z tym nie zadziała i będziesz musiał wykonać iterację ręcznie w ten sposób:źródło
foreach
obsługę Twojej hipotetycznejIAsyncEnumerator<T>
?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:Możemy osiągnąć to samo zachowanie w C # 5, ale z inną semantyką:
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.
źródło
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.
źródło
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:
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.
źródło
Był plan
https://github.com/dotnet/csharplang/issues/43
Ale obecnie nie jest to możliwe
źródło
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
DownloadAllHtml
funkcji w inny sposób.Na przykład możesz użyć czegoś takiego:
Nie oznacza to, że
DownloadAllUrl
funkcja NIE jest wywołaniem asynchronicznym. Ale możesz mieć wywołanie asynchroniczne zaimplementowane w innej funkcji (tjDownloadHtmlAsync
.).Biblioteka równoległa zadań ma funkcje
.ContinueWhenAny
i.ContinueWhenAll
.Można tego użyć w następujący sposób:
źródło
Task<IEnumerable<Task<string>>> DownloadAllUrl
. Lub, jeśli chcesz, aby akcje „stopki”IEnumerable<Task>
. Np. Gist.github.com/1184435async
wszystko jest skończone i C # 5.0 jest wydany, można to zaktualizować.Niestety plony nie działają z oczekiwaniem. Ale po to jest Rx. Sprawdź https://msdn.microsoft.com/library/hh242985
źródło
To rozwiązanie działa zgodnie z oczekiwaniami. Zwróć uwagę na
await Task.Run(() => enumerator.MoveNext())
część.źródło