Mam kilka asynchronicznych usług REST, które nie są od siebie zależne. To znaczy, „czekając” na odpowiedź Service1, mogę zadzwonić do Service2, Service3 i tak dalej.
Na przykład zapoznaj się z poniższym kodem:
var service1Response = await HttpService1Async();
var service2Response = await HttpService2Async();
// Use service1Response and service2Response
Teraz service2Response
nie zależy od nich service1Response
i można je pobrać niezależnie. Dlatego nie muszę czekać na odpowiedź pierwszej usługi, aby zadzwonić do drugiej usługi.
Nie sądzę, żebym mógł Parallel.ForEach
tutaj użyć , ponieważ nie jest to operacja związana z procesorem.
Czy mogę wywołać te dwie operacje równolegle, czy mogę wywołać use Task.WhenAll
? Jednym z problemów, z których korzystam, Task.WhenAll
jest to, że nie zwraca wyników. Aby pobrać wynik, czy mogę zadzwonić task.Result
po wywołaniu Task.WhenAll
, ponieważ wszystkie zadania są już zakończone i wystarczy pobrać odpowiedź?
Przykładowy kod:
var task1 = HttpService1Async();
var task2 = HttpService2Async();
await Task.WhenAll(task1, task2)
var result1 = task1.Result;
var result2 = task2.Result;
// Use result1 and result2
Czy ten kod jest lepszy od pierwszego pod względem wydajności? Jakieś inne podejście, którego mogę użyć?
źródło
I do not think I can use Parallel.ForEach here since it is not CPU bound operation
- Nie widzę tam logiki. Współbieżność to współbieżność.Parallel.ForEach
pojawiłyby się nowe wątki, podczasasync await
gdy wszystko robiłoby w jednym wątku.await
), zanim będzie gotowy.WhenAll
zanim skończęResult
z pomysłem, że wykona wszystkie zadania przed wywołaniem funkcji .Result. Ponieważ Task.Result blokuje wątek wywołujący, zakładam, że jeśli wywołam go po zakończeniu zadań, natychmiast zwróci wynik. Chcę potwierdzić zrozumienie.Odpowiedzi:
Ale to nie zwróci wyniki. Wszystkie będą w tablicy tego samego typu, więc nie zawsze przydatne jest użycie wyników, w których musisz znaleźć element w tablicy, który odpowiada
Task
wynikowi, dla którego chcesz uzyskać wynik, i potencjalnie rzuć go na jego rzeczywisty typ, więc może nie być to najłatwiejsze / najbardziej czytelne podejście w tym kontekście, ale jeśli chcesz mieć wszystkie wyniki z każdego zadania, a typowym typem jest typ, którym chcesz je traktować, to świetnie .Tak, możesz to zrobić. Mógłbyś także
await
je (await
rozpakowałby wyjątek w każdym zadaniu z błędem, natomiastResult
wygenerowałby wyjątek zbiorczy, ale w przeciwnym razie byłby taki sam).Wykonuje dwie operacje jednocześnie, zamiast jednej, a potem drugiej. To, czy jest to lepsze czy gorsze, zależy od tego, jakie są te operacje leżące u podstaw. Jeśli podstawowymi operacjami są „odczytywanie pliku z dysku”, wykonywanie ich równolegle jest prawdopodobnie wolniejsze, ponieważ istnieje tylko jedna głowica dysku i może znajdować się tylko w jednym miejscu w danym momencie; przeskakiwanie między dwoma plikami będzie wolniejsze niż czytanie jednego pliku po drugim. Z drugiej strony, jeśli operacje będą „wykonywać niektóre żądania sieciowe” (jak ma to miejsce w tym przypadku), prawdopodobnie będą one szybsze (przynajmniej do pewnej liczby równoczesnych żądań), ponieważ możesz poczekać na odpowiedź z innego komputera sieciowego tak samo szybko, gdy trwa również inne oczekujące żądanie sieciowe. Jeśli chcesz wiedzieć, czy to ”
Jeśli nie jest dla Ciebie ważne, że znasz wszystkie wyjątki zgłoszone między wszystkimi operacjami wykonywanymi równolegle, a nie tylko pierwszą, możesz po prostu wykonać
await
zadaniaWhenAll
w ogóle. Jedyną rzeczą, jakąWhenAll
dajesz, jest posiadanieAggregateException
każdego wyjątku od każdego błędnego zadania, zamiast rzucania, gdy trafisz pierwsze błędne zadanie. To tak proste, jak:źródło
Oto metoda rozszerzenia, która wykorzystuje SemaphoreSlim i pozwala ustawić maksymalny stopień równoległości
Przykładowe użycie:
źródło
Możesz albo użyć
lub
źródło