// let's say there is a list of 1000+ URLs
string[] urls = { "http://google.com", "http://yahoo.com", ... };
// now let's send HTTP requests to each of these URLs in parallel
urls.AsParallel().ForAll(async (url) => {
var client = new HttpClient();
var html = await client.GetStringAsync(url);
});
Oto problem, uruchamia ponad 1000 jednoczesnych żądań internetowych. Czy istnieje łatwy sposób ograniczenia równoczesnej liczby tych asynchronicznych żądań http? Tak, aby w danym momencie pobieranych było nie więcej niż 20 stron internetowych. Jak to zrobić najefektywniej?
c#
asynchronous
task-parallel-library
async-ctp
async-await
Grief Coder
źródło
źródło
HttpClient
jestIDisposable
i powinieneś się go pozbyć, zwłaszcza gdy masz zamiar użyć ich ponad 1000.HttpClient
może być używany jako singleton dla wielu żądań.Odpowiedzi:
Z pewnością możesz to zrobić w najnowszych wersjach async dla .NET, używając .NET 4.5 Beta. Poprzedni post z „usr” wskazuje na dobry artykuł napisany przez Stephena Touba, ale mniej ogłoszoną wiadomością jest to, że semafor asynchroniczny faktycznie trafił do wersji beta .NET 4.5
Jeśli spojrzysz na naszą ukochaną
SemaphoreSlim
klasę (której powinieneś używać, ponieważ jest bardziej wydajna niż oryginalnaSemaphore
), teraz oferujeWaitAsync(...)
serię przeciążeń, ze wszystkimi oczekiwanymi argumentami - przedziały czasu, tokeny anulowania, wszyscy twoi znajomi z harmonogramu: )Stephen napisał również nowszy wpis na blogu dotyczący nowych dodatków .NET 4.5, które pojawiły się wraz z wersją beta, zobacz Co nowego w paralelizmie w .NET 4.5 Beta .
Na koniec, oto przykładowy kod dotyczący używania SemaphoreSlim do ograniczania metody asynchronicznej:
Na koniec warto wspomnieć o rozwiązaniu wykorzystującym planowanie w oparciu o TPL. W TPL można tworzyć zadania powiązane z delegatem, które nie zostały jeszcze uruchomione, i zezwolić niestandardowemu harmonogramowi zadań na ograniczenie współbieżności. W rzeczywistości jest tutaj próbka MSDN:
Zobacz także TaskScheduler .
źródło
HttpClient
Parallel.ForEach
działa z kodem synchronicznym. Umożliwia to wywołanie kodu asynchronicznego.IDisposable
swusing
lubtry-finally
oświadczeń, oraz zapewnić ich dyspozycji.Jeśli masz IEnumerable (tj. Ciągi adresów URL) i chcesz wykonać operację związaną z we / wy z każdym z nich (tj. Wykonać asynchroniczne żądanie http) jednocześnie ORAZ opcjonalnie chcesz również ustawić maksymalną liczbę równoczesnych Żądania we / wy w czasie rzeczywistym, oto jak możesz to zrobić. W ten sposób nie używasz puli wątków i innych, metoda używa semaforeslim do kontrolowania maksymalnej liczby równoczesnych żądań we / wy, podobnie jak we wzorcu przesuwanego okna, które kończy jedno żądanie, opuszcza semafor, a następne dostaje się.
użycie: await ForEachAsync (urlStrings, YourAsyncFunc, optionalMaxDegreeOfConcurrency);
źródło
using
Byłoby miło.Niestety w .NET Framework brakuje najważniejszych kombinatorów do organizowania równoległych zadań asynchronicznych. Nie ma czegoś takiego wbudowanego.
Spójrz na klasę AsyncSemaphore zbudowaną przez najbardziej szanowanego Stephena Touba. To, czego chcesz, nazywa się semaforem i potrzebujesz jego asynchronicznej wersji.
źródło
Istnieje wiele pułapek, a bezpośrednie użycie semafora może być trudne w przypadku błędów, dlatego sugerowałbym użycie AsyncEnumerator pakietu NuGet zamiast ponownego wymyślania koła:
źródło
Przykład Theo Yaunga jest fajny, ale istnieje wariant bez listy zadań oczekujących.
źródło
ProccessUrl
lub jego podfunkcjach, zostaną w rzeczywistości zignorowane. Zostaną przechwycone do zadań, ale nie zostaną przeniesione z powrotem do pierwotnego rozmówcyCheck(...)
. Osobiście dlatego nadal używam zadań i ich funkcji kombinatorów, takich jakWhenAll
iWhenAny
- aby uzyskać lepszą propagację błędów. :)SemaphoreSlim może być tutaj bardzo pomocny. Oto utworzona przeze mnie metoda rozszerzenia.
Przykładowe użycie:
źródło
Stare pytanie, nowa odpowiedź. @vitidev miał blok kodu, który został ponownie użyty w prawie nienaruszonym stanie w recenzowanym przeze mnie projekcie. Po rozmowie z kilkoma współpracownikami jeden z nich zapytał: „Dlaczego nie użyjesz po prostu wbudowanych metod OC?” ActionBlock wygląda tam na zwycięzcę. https://msdn.microsoft.com/en-us/library/hh194773(v=vs.110).aspx . Prawdopodobnie nie zmieni żadnego istniejącego kodu, ale na pewno spróbuje zaadoptować ten nuget i ponownie wykorzystać najlepszą praktykę pana Softy'ego w zakresie ograniczonego równoległości.
źródło
Oto rozwiązanie, które wykorzystuje leniwy charakter LINQ. Jest funkcjonalnie równoważne z zaakceptowaną odpowiedzią ), ale używa zadań roboczych zamiast a
SemaphoreSlim
, zmniejszając w ten sposób ślad pamięci całej operacji. Na początku pozwólmy mu działać bez dławienia. Pierwszym krokiem jest przekonwertowanie naszych adresów URL na niezliczone zadania.Drugim krokiem jest wykonanie
await
wszystkich zadań jednocześnie przy użyciuTask.WhenAll
metody:Wynik:
Wdrożenie Microsoft od
Task.WhenAll
materializuje natychmiast dostarczony przeliczalny do tablicy, powodując wszystkie zadania do rozruchów na raz. Nie chcemy tego, ponieważ chcemy ograniczyć liczbę współbieżnych operacji asynchronicznych. Musimy więc wdrożyć alternatywęWhenAll
, która wyliczy nasze wyliczalne delikatnie i powoli. Zrobimy to, tworząc liczbę zadań roboczych (równych pożądanemu poziomowi współbieżności), a każde zadanie robocze będzie wyliczać jedno zadanie na raz, używając blokady, aby zapewnić, że każde zadanie adresu URL zostanie przetworzone tylko jednym zadaniem roboczym. Następnieawait
wykonujemy wszystkie zadania pracownika, a na koniec zwracamy wyniki. Oto realizacja:... a oto, co musimy zmienić w naszym początkowym kodzie, aby osiągnąć pożądane dławienie:
Istnieje różnica w obsłudze wyjątków. Natywny
Task.WhenAll
czeka na zakończenie wszystkich zadań i agreguje wszystkie wyjątki. Powyższa implementacja kończy się natychmiast po zakończeniu pierwszego błędnego zadania.źródło
IAsyncEnumerable<T>
można znaleźć tutaj .Chociaż 1000 zadań może zostać umieszczonych w kolejce bardzo szybko, biblioteka zadań równoległych może obsługiwać tylko zadania współbieżne równe liczbie rdzeni procesora w komputerze. Oznacza to, że jeśli masz maszynę czterordzeniową, tylko 4 zadania będą wykonywane w danym czasie (chyba że obniżysz MaxDegreeOfParallelism).
źródło
await
słowa kluczowego. Usunięcie tego powinno rozwiązać problem, prawda?Running
statusem) jednocześnie niż liczba rdzeni. Będzie to szczególnie dotyczyło zadań związanych z We / Wy.W celu przyspieszenia operacji związanych z procesorem należy stosować obliczenia równoległe. Tutaj mówimy o operacjach związanych z I / O. Twoja implementacja powinna być całkowicie asynchroniczna , chyba że przytłaczasz zajęty pojedynczy rdzeń wielordzeniowego procesora.
EDYCJA Podoba mi się sugestia usr dotycząca użycia „asynchronicznego semafora”.
źródło
Użyj
MaxDegreeOfParallelism
, która jest opcją, którą możesz określić wParallel.ForEach()
:źródło
GetStringAsync(url)
jest przeznaczony do wywołaniaawait
. Jeśli sprawdzisz typvar html
, to jest to aTask<string>
, a nie wynikstring
.Parallel.ForEach(...)
jest przeznaczony do równoległego uruchamiania bloków kodu synchronicznego (np. w różnych wątkach).Zasadniczo będziesz chciał utworzyć akcję lub zadanie dla każdego adresu URL, który chcesz trafić, umieścić je na liście, a następnie przetworzyć tę listę, ograniczając liczbę, którą można przetwarzać równolegle.
Mój post na blogu pokazuje, jak to zrobić zarówno za pomocą zadań, jak i akcji, oraz zawiera przykładowy projekt, który można pobrać i uruchomić, aby zobaczyć oba w akcji.
Z akcjami
Jeśli korzystasz z akcji, możesz użyć wbudowanej funkcji .Net Parallel.Invoke. Tutaj ograniczamy to do równoległego uruchamiania maksymalnie 20 wątków.
Z zadaniami
W przypadku zadań nie ma wbudowanej funkcji. Możesz jednak skorzystać z tego, który udostępniam na moim blogu.
A następnie tworząc listę zadań i wywołując funkcję w celu ich uruchomienia, powiedzmy maksymalnie 20 jednocześnie, możesz zrobić to:
źródło
nie jest to dobra praktyka, ponieważ zmienia globalną zmienną. nie jest to również ogólne rozwiązanie dla asynchronii. ale jest to łatwe dla wszystkich wystąpień HttpClient, jeśli to wszystko, czego szukasz. możesz po prostu spróbować:
źródło