Wykonuj operacje asynchroniczne w ASP.NET MVC, używając wątku z ThreadPool na platformie .NET 4

158

Po tym pytaniu czuję się komfortowo podczas korzystania z operacji asynchronicznych w ASP.NET MVC. Napisałem więc dwa posty na blogu na ten temat:

Mam w głowie zbyt wiele nieporozumień dotyczących operacji asynchronicznych w ASP.NET MVC.

Zawsze słyszę to zdanie: aplikacja może się lepiej skalować, jeśli operacje są wykonywane asynchronicznie

Często też słyszałem tego rodzaju zdania: jeśli masz duży ruch, lepiej nie wykonywać zapytań asynchronicznie - zużywanie 2 dodatkowych wątków do obsługi jednego żądania zabiera zasoby innym żądaniom przychodzącym.

Myślę, że te dwa zdania są niespójne.

Nie mam zbyt wielu informacji o tym, jak działa Threadpool w ASP.NET, ale wiem, że Threadpool ma ograniczony rozmiar dla wątków. Zatem drugie zdanie musi odnosić się do tej kwestii.

I chciałbym wiedzieć, czy operacje asynchroniczne w ASP.NET MVC używają wątku z ThreadPool na .NET 4?

Na przykład, kiedy implementujemy AsyncController, w jaki sposób struktury aplikacji? Jeśli otrzymuję duży ruch, czy warto zaimplementować AsyncController?

Czy jest ktoś, kto może zdjąć tę czarną zasłonę na moich oczach i wyjaśnić mi ofertę asynchronii w ASP.NET MVC 3 (NET 4)?

Edytować:

Przeczytałem poniższy dokument prawie setki razy i rozumiem główną sprawę, ale nadal mam zamieszanie, ponieważ jest tam zbyt wiele niespójnych komentarzy.

Korzystanie z kontrolera asynchronicznego w ASP.NET MVC

Edytować:

Załóżmy, że mam akcję kontrolera jak poniżej (nie jest to jednak implementacja AsyncController):

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Jak widzisz, odpalam operację i zapominam o niej. Następnie wracam natychmiast, nie czekając na zakończenie.

W tym przypadku, czy to musi używać wątku z puli wątków? Jeśli tak, po zakończeniu, co się stanie z tym wątkiem? Czy GCpojawia się i czyści zaraz po zakończeniu?

Edytować:

Aby uzyskać odpowiedź @ Darin, oto przykład kodu asynchronicznego, który komunikuje się z bazą danych:

public class FooController : AsyncController {

    //EF 4.2 DbContext instance
    MyContext _context = new MyContext();

    public void IndexAsync() { 

        AsyncManager.OutstandingOperations.Increment(3);

        Task<IEnumerable<Foo>>.Factory.StartNew(() => { 

           return 
                _context.Foos;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foos"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<Bars>>.Factory.StartNew(() => { 

           return 
                _context.Bars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["bars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });

        Task<IEnumerable<FooBar>>.Factory.StartNew(() => { 

           return 
                _context.FooBars;
        }).ContinueWith(t => {

            AsyncManager.Parameters["foobars"] = t.Result;
            AsyncManager.OutstandingOperations.Decrement();
        });
    }

    public ViewResult IndexCompleted(
        IEnumerable<Foo> foos, 
        IEnumerable<Bar> bars,
        IEnumerable<FooBar> foobars) {

        //Do the regular stuff and return

    }
}
tugberk
źródło
Nie jestem pewien odpowiedzi, ale warto zauważyć, że asynchroniczne i wielowątkowe to różne rzeczy. Byłoby więc możliwe posiadanie stałej liczby wątków z obsługą asynchroniczną. Co by się stało, gdyby jedna strona musiała blokować, powiedzmy, I / O, inna strona miałaby szansę uruchomić się w tym samym wątku. W ten sposób oba te stwierdzenia mogą być prawdziwe, asynchronizacja może przyspieszyć działanie, ale zbyt wiele wątków jest problemem.
Chris Chilvers,
@ChrisChilvers Tak, wielowątkowość nie zawsze jest konieczna w przypadku operacji asynchronicznej. Już to wymyśliłem, ale myślę, że nie mam nad tym kontroli, o ile wiem. AsyncController podkręca, ile wątków chce z mojego punktu widzenia, ale też nie jestem pewien. Czy istnieje pojęcie puli wątków w aplikacjach komputerowych, takich jak WPF? Myślę, że liczba wątków nie jest problemem w tego rodzaju aplikacjach.
tugberk
6
Obejrzyj wideo
Wątki
Myślę, że problem (a tym samym niespójność) polega na tym, że druga instrukcja używa asynchronicznego, gdy oznacza wiele wątków. Może to być spowodowane tym, że w ten sposób platforma asp.net zaimplementowała strony asynchroniczne, a zatem konkretna implementacja zmyliła problem (ponieważ nazwa funkcji powodującej problem to strony asynchroniczne), ale nie jestem pewien co do konkretnej implementacji . Więc albo mają na myśli „wiele wątków”, albo „asynchroniczne strony w asp.net w wersji X”, ponieważ przyszłe wersje mogą zmienić implementację. Lub po prostu mają na myśli użycie puli wątków do wykonywania asynchronizacji na stronie.
Chris Chilvers,
@ChrisChilvers oh, stary! Jestem bardziej zdezorientowany po tych komentarzach: s
tugberk

Odpowiedzi:

177

Oto doskonały artykuł , który polecam przeczytać, aby lepiej zrozumieć przetwarzanie asynchroniczne w ASP.NET (co zasadniczo reprezentują kontrolery asynchroniczne).

Rozważmy najpierw standardową akcję synchroniczną:

public ActionResult Index()
{
    // some processing
    return View();
}

Gdy żądanie jest skierowane do tej akcji, z puli wątków jest rysowany wątek, a treść tej akcji jest wykonywana w tym wątku. Więc jeśli przetwarzanie w ramach tej akcji jest powolne, blokujesz ten wątek dla całego przetwarzania, więc ten wątek nie może być ponownie użyty do przetwarzania innych żądań. Pod koniec wykonywania żądania wątek jest zwracany do puli wątków.

Teraz weźmy przykład wzorca asynchronicznego:

public void IndexAsync()
{
    // perform some processing
}

public ActionResult IndexCompleted(object result)
{
    return View();
}

Gdy żądanie jest wysyłane do akcji Index, wątek jest rysowany z puli wątków i IndexAsyncwykonywana jest treść metody. Po zakończeniu wykonywania treści tej metody wątek jest zwracany do puli wątków. Następnie, używając standardu AsyncManager.OutstandingOperations, po zasygnalizowaniu zakończenia operacji asynchronicznej, z puli wątków jest rysowany kolejny wątek i IndexCompletedwykonywana jest na nim treść akcji, a wynik renderowany klientowi.

Widzimy więc w tym wzorcu, że pojedyncze żądanie HTTP klienta może zostać wykonane przez dwa różne wątki.

Teraz interesująca część dzieje się wewnątrz IndexAsyncmetody. Jeśli masz w sobie operację blokującą, całkowicie marnujesz cały cel kontrolerów asynchronicznych, ponieważ blokujesz wątek roboczy (pamiętaj, że treść tej akcji jest wykonywana na wątku narysowanym z puli wątków).

Kiedy więc możemy naprawdę skorzystać z kontrolerów asynchronicznych, o które możesz zapytać?

IMHO możemy zyskać najwięcej, gdy mamy intensywne operacje we / wy (takie jak bazy danych i wywołania sieciowe do usług zdalnych). Jeśli masz operację intensywnie wykorzystującą procesor, akcje asynchroniczne nie przyniosą wielu korzyści.

Dlaczego więc możemy czerpać korzyści z intensywnych operacji we / wy? Ponieważ moglibyśmy użyć portów zakończenia we / wy . IOCP są niezwykle potężne, ponieważ nie zużywasz żadnych wątków ani zasobów na serwerze podczas wykonywania całej operacji.

Jak oni pracują?

Załóżmy, że chcemy pobrać zawartość zdalnej strony internetowej przy użyciu metody WebClient.DownloadStringAsync . Wywołujesz tę metodę, która zarejestruje IOCP w systemie operacyjnym i natychmiast powróci. Podczas przetwarzania całego żądania na serwerze nie są używane żadne wątki. Wszystko dzieje się na zdalnym serwerze. Może to zająć dużo czasu, ale nie przejmujesz się, ponieważ nie narażasz swoich wątków roboczych. Po odebraniu odpowiedzi sygnalizowany jest IOCP, z puli wątków pobierany jest wątek i w tym wątku wykonywane jest wywołanie zwrotne. Ale jak widać, podczas całego procesu nie zmonopolizowaliśmy żadnych wątków.

To samo dotyczy metod takich jak FileStream.BeginRead, SqlCommand.BeginExecute, ...

A co z równoległością wielu wywołań bazy danych? Załóżmy, że masz synchroniczną akcję kontrolera, w której wykonałeś kolejno 4 blokujące wywołania bazy danych. Łatwo obliczyć, że jeśli każde wywołanie bazy danych trwa 200 ms, wykonanie akcji kontrolera zajmie około 800 ms.

Jeśli nie musisz uruchamiać tych wywołań po kolei, czy zrównoleglenie ich poprawi wydajność?

To jest wielkie pytanie, na które nie jest łatwo odpowiedzieć. Może tak może nie. Będzie to całkowicie zależało od tego, jak zaimplementujesz te wywołania bazy danych. Jeśli używasz kontrolerów asynchronicznych i portów zakończenia we / wy, jak omówiono wcześniej, zwiększysz wydajność tej akcji kontrolera, a także innych akcji, ponieważ nie będziesz monopolizować wątków roboczych.

Z drugiej strony, jeśli zaimplementujesz je słabo (z blokującym wywołaniem bazy danych wykonywanym na wątku z puli wątków), zasadniczo zmniejszysz całkowity czas wykonywania tej akcji do około 200 ms, ale zużywałbyś 4 wątki robocze, więc mogło obniżyć wydajność innych żądań, które mogą stać się głodne z powodu brakujących wątków w puli do ich przetwarzania.

Jest to więc bardzo trudne i jeśli nie czujesz się gotowy do wykonywania obszernych testów swojej aplikacji, nie wdrażaj kontrolerów asynchronicznych, ponieważ istnieje szansa, że ​​wyrządzisz więcej szkód niż korzyści. Wdrażaj je tylko wtedy, gdy masz ku temu powód: na przykład zidentyfikowałeś, że standardowe akcje kontrolera synchronicznego są wąskim gardłem dla twojej aplikacji (oczywiście po wykonaniu obszernych testów obciążenia i pomiarów).

Rozważmy teraz Twój przykład:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

Po odebraniu żądania dotyczącego akcji indeksu wątek jest rysowany z puli wątków w celu wykonania jego treści, ale jego treść planuje tylko nowe zadanie przy użyciu TPL . Tak więc wykonywanie akcji kończy się, a wątek jest zwracany do puli wątków. Poza tym TPL używa wątków z puli wątków do wykonywania ich przetwarzania. Więc nawet jeśli oryginalny wątek został zwrócony do puli wątków, narysowano inny wątek z tej puli w celu wykonania treści zadania. Więc naraziłeś 2 nici ze swojej cennej puli.

Rozważmy teraz następujące kwestie:

public ViewResult Index() { 

    new Thread(() => { 
        //Do an advanced looging here which takes a while
    }).Start();

    return View();
}

W tym przypadku ręcznie tworzymy wątek. W takim przypadku wykonanie treści akcji Indeks może potrwać nieco dłużej (ponieważ utworzenie nowego wątku jest droższe niż pobranie wątku z istniejącej puli). Ale wykonanie zaawansowanej operacji rejestrowania zostanie wykonane w wątku, który nie jest częścią puli. Dlatego nie zagrażamy wątkom z puli, które pozostają wolne do obsługi innych żądań.

Darin Dimitrov
źródło
1
Naprawdę szczegółowe, dzięki! Załóżmy, że mamy 4 async Tasks ( System.Threading.Task) uruchomione wewnątrz IndexAsyncmetody. W ramach tych operacji wykonujemy wywołania bazy danych do serwera. Więc wszystkie z nich wymagają intensywnych operacji we / wy, prawda? W takim przypadku, czy tworzymy 4 oddzielne wątki (lub otrzymujemy 4 oddzielne wątki z puli wątków)? Zakładając, że mam maszynę wielordzeniową, które będą działać równolegle, prawda?
tugberk
10
@tugberk, wywołania bazy danych są operacjami we / wy, ale wszystko będzie zależeć od tego, jak je zaimplementujesz. Jeśli używasz blokującego wywołania bazy danych, takiego jak SqlCommand.ExecuteReadermarnujesz wszystko, ponieważ jest to połączenie blokujące. Blokujesz wątek, na którym jest wykonywane to wywołanie, a jeśli ten wątek jest wątkiem z puli, jest to bardzo złe. Zyskasz tylko jeśli używasz I / O Ports Zakończenie: SqlCommand.BeginExecuteReader. Jeśli nie używasz IOCP bez względu na to, co robisz, nie używaj kontrolerów asynchronicznych, ponieważ wyrządzisz więcej szkód niż zyskasz na ogólnej wydajności aplikacji.
Darin Dimitrov,
1
Cóż, przez większość czasu najpierw używam kodu EF. Nie jestem pewien, czy pasuje. Umieściłem próbkę, która pokazuje, czym się ogólnie zajmuję. Zaktualizowałem pytanie, czy możesz spojrzeć?
holownik
3
@tugberk, uruchamiasz je równolegle, więc całkowity czas wykonywania jest mniejszy w porównaniu do tego, gdy uruchamiasz je sekwencyjnie. Ale aby je uruchomić, używasz wątków roboczych. Właściwie EF jest leniwy, więc kiedy to robisz _context.Foo, tak naprawdę nic nie wykonujesz. Po prostu budujesz drzewo wyrażeń. Bądź z tym bardzo ostrożny. Wykonanie zapytania jest odroczone tylko wtedy, gdy zaczniesz wyliczanie na zestawie wyników. A jeśli zdarzy się to w widoku, może to być katastrofalne dla wydajności. Aby chętnie wykonać zapytanie EF .ToList()na końcu.
Darin Dimitrov,
2
@tugberk, będziesz potrzebować narzędzia do testowania obciążenia, aby symulować równolegle wielu użytkowników swojej witryny internetowej, aby zobaczyć, jak zachowuje się pod dużym obciążeniem. Mini Profiler nie może symulować ładowania Twojej witryny. Może pomóc Ci zobaczyć i zoptymalizować zapytania ADO.NET i profilować pojedyncze żądanie, co jest bezużyteczne, gdy chcesz zobaczyć, jak zachowuje się Twoja witryna w rzeczywistej sytuacji, gdy trafia do niej wielu użytkowników.
Darin Dimitrov,
49

Tak - wszystkie wątki pochodzą z puli wątków. Twoja aplikacja MVC jest już wielowątkowa, gdy żądanie pojawi się w nowym wątku, zostanie pobrany z puli i użyty do obsługi żądania. Ten wątek będzie „zablokowany” (z powodu innych żądań), dopóki żądanie nie zostanie w pełni obsłużone i zakończone. Jeśli w puli nie ma dostępnego wątku, żądanie będzie musiało poczekać, aż jeden będzie dostępny.

Jeśli masz kontrolery asynchroniczne, nadal otrzymują wątek z puli, ale podczas obsługi żądania mogą zrezygnować z wątku, czekając, aż coś się wydarzy (i ten wątek może zostać przekazany do innego żądania) i kiedy pierwotne żądanie wymaga wątku znowu dostaje jeden z puli.

Różnica polega na tym, że jeśli masz wiele długotrwałych żądań (gdzie wątek czeka na odpowiedź), możesz zabraknąć wątków z puli do obsługi nawet podstawowych żądań. Jeśli masz kontrolery asynchroniczne, nie masz więcej wątków, ale wątki, które oczekują, są zwracane do puli i mogą obsługiwać inne żądania.

Prawie prawdziwym przykładem życia ... Pomyśl o tym jak się w autobusie, tam pięć osób oczekujących, aby dostać się na pierwszy wsiada, płaci i siada (kierowca serwisowane ich prośbę), można dostać się na (kierowca serwisowania Twoja prośba), ale nie możesz znaleźć swoich pieniędzy; jak będziesz grzebać w kieszeniach, kierowca rezygnuje z Ciebie i wciąga kolejne dwie osoby (obsługa ich zgłoszeń), gdy znajdziesz swoje pieniądze, kierowca znowu zaczyna się z Tobą zajmować (realizuje Twoją prośbę) - piąta osoba musi czekać do skończyłeś, ale trzecia i czwarta osoba została obsłużona, gdy byłeś w połowie obsłużenia. Oznacza to, że kierowca jest jedynym wątkiem z puli, a pasażerami są prośby. Pisanie, jak by to działało, gdyby było dwóch kierowców, było zbyt skomplikowane, ale możesz sobie wyobrazić ...

Bez kontrolera asynchronicznego pasażerowie za Tobą musieliby czekać wieki, aż szukałeś swoich pieniędzy, a kierowca autobusu nie pracowałby.

Wniosek jest taki, że jeśli wiele osób nie wie, gdzie są ich pieniądze (tj. Potrzebują dużo czasu, aby odpowiedzieć na coś, o co poprosił kierowca), kontrolery asynchroniczne mogą również pomóc w przepustowości żądań, przyspieszając ten proces od niektórych. Bez kontrolera aysnc wszyscy czekają, aż osoba z przodu zostanie całkowicie rozwiązana. ALE nie zapominaj, że w MVC masz wielu kierowców autobusów w jednym autobusie, więc asynchronizacja nie jest automatycznym wyborem.

K. Bob
źródło
8
Bardzo fajna analogia. Dziękuję Ci.
Pittsburgh DBA
Podobał mi się opis. Dzięki
Omer Cansizoglu
Doskonały sposób na wyjaśnienie. Dziękuję,
Anand Vyas,
Twoja odpowiedź w połączeniu z odpowiedzią Darina podsumowuje cały mechanizm kontrolerów asynchronicznych, co to jest, a co ważniejsze, czym nie jest!
Nirman
To fajna analogia, ale moje jedyne pytanie jest takie: facet grzebący w kieszeniach w twojej analogii byłby czymś w rodzaju pracy / przetwarzania w naszej aplikacji ... więc jakie procesy działają, gdy zwolnimy wątek? Niegrzecznie to kolejny wątek? Więc co tu zyskujemy?
Tomuke
10

W grę wchodzą dwie koncepcje. Przede wszystkim możemy sprawić, by nasz kod działał równolegle, aby wykonywać go szybciej lub zaplanować kod w innym wątku, aby użytkownik nie musiał czekać. Przykład, który miałeś

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Do an advanced looging here which takes a while
    });

    return View();
}

należy do drugiej kategorii. Użytkownik uzyska szybszą odpowiedź, ale całkowite obciążenie serwera jest większe, ponieważ musi on wykonywać tę samą pracę + obsługiwać wątki.

Innym przykładem może być:

public ViewResult Index() { 

    Task.Factory.StartNew(() => { 
        //Make async web request to twitter with WebClient.DownloadString()
    });

    Task.Factory.StartNew(() => { 
        //Make async web request to facebook with WebClient.DownloadString()
    });


    //wait for both to be ready and merge the results

    return View();
}

Ponieważ żądania są wykonywane równolegle, użytkownik nie będzie musiał czekać tak długo, jak gdyby były wykonywane szeregowo. Ale powinieneś zdać sobie sprawę, że zużywamy tutaj więcej zasobów, niż gdybyśmy uruchamiali szeregowo, ponieważ uruchamiamy kod w wielu wątkach, podczas gdy mamy również wątek oczekujący.

W przypadku klienta jest to całkowicie w porządku. Często zdarza się, że synchroniczny, długo działający kod jest zawijany w nowe zadanie (uruchamianie go w innym wątku), aby interfejs użytkownika był responsywny lub paralizowany, aby przyspieszyć. Jednak wątek jest nadal używany przez cały czas trwania. Na serwerze z dużym obciążeniem może to przynieść odwrotny skutek, ponieważ w rzeczywistości zużywa się więcej zasobów. Przed tym ludzie cię ostrzegali

Kontrolery asynchroniczne w MVC mają jednak inny cel. Chodzi o to, aby uniknąć sytuacji, w których nic nie robi nic (co może zaszkodzić skalowalności). Naprawdę ma to znaczenie tylko wtedy, gdy wywoływany interfejs API ma metody asynchroniczne. Podobnie jak WebClient.DowloadStringAsync ().

Chodzi o to, że możesz pozwolić, aby Twój wątek został zwrócony do obsługi nowych żądań, dopóki żądanie sieciowe nie zostanie zakończone, w którym wywoła wywołanie zwrotne, które otrzyma ten sam lub nowy wątek i zakończy żądanie.

Mam nadzieję, że rozumiesz różnicę między asynchronicznym a równoległym. Pomyśl o kodzie równoległym jako kodzie, w którym znajduje się twój wątek i czekaj na wynik. Podczas gdy kod asynchroniczny to kod, w którym zostaniesz powiadomiony, gdy kod zostanie ukończony i możesz wrócić do pracy nad nim, w międzyczasie wątek może wykonać inną pracę.

Mikael Eliasson
źródło
6

Aplikacje można lepiej skalować, jeśli operacje są wykonywane asynchronicznie, ale tylko wtedy, gdy dostępne są zasoby do obsługi dodatkowych operacji .

Operacje asynchroniczne zapewniają, że nigdy nie blokujesz akcji, ponieważ istniejąca jest w toku. ASP.NET ma model asynchroniczny, który umożliwia wykonywanie wielu żądań równolegle. Możliwe byłoby kolejkowanie żądań i przetwarzanie ich w trybie FIFO, ale nie byłoby to dobrze skalowane, gdy w kolejce są setki żądań, a przetworzenie każdego żądania zajmuje 100 ms.

Jeśli masz ogromną ilość ruchu, to może lepiej nie wykonywania zapytań asynchronicznie, ponieważ nie może być żadnych dodatkowych zasobów do obsługi żądań . Jeśli nie ma wolnych zasobów, żądania są zmuszane do kolejkowania, trwają wykładniczo dłużej lub wręcz kończą się niepowodzeniem, w którym to przypadku asynchroniczne obciążenie (muteksy i operacje przełączania kontekstu) niczego nie daje.

Jeśli chodzi o ASP.NET, nie masz wyboru - używa modelu asynchronicznego, ponieważ to ma sens w modelu serwer-klient. Jeśli miałbyś pisać własny kod wewnętrznie, który używa wzorca asynchronicznego, aby spróbować lepiej skalować, chyba że próbujesz zarządzać zasobem współdzielonym między wszystkimi żądaniami, w rzeczywistości nie zobaczysz żadnych ulepszeń, ponieważ są one już opakowane w procesie asynchronicznym, który nie blokuje niczego innego.

Ostatecznie wszystko jest subiektywne, dopóki nie przyjrzysz się temu, co powoduje wąskie gardło w twoim systemie. Czasami jest oczywiste, gdzie pomoże wzorzec asynchroniczny (zapobiegając blokowaniu zasobów w kolejce). Ostatecznie tylko pomiar i analiza systemu może wskazać, gdzie można zwiększyć wydajność.

Edytować:

W tym przykładzie Task.Factory.StartNewwywołanie spowoduje kolejkowanie operacji w puli wątków platformy .NET. Wątki puli wątków mają być ponownie wykorzystywane (aby uniknąć kosztów tworzenia / niszczenia wielu wątków). Po zakończeniu operacji wątek jest zwalniany z powrotem do puli, aby mógł zostać ponownie użyty przez inne żądanie (moduł Garbage Collector w rzeczywistości nie jest zaangażowany, chyba że utworzyłeś jakieś obiekty w swoich operacjach, w którym to przypadku są one zbierane w normalny sposób zakres).

Jeśli chodzi o ASP.NET, nie ma tutaj specjalnej operacji. Żądanie ASP.NET kończy się bez względu na zadanie asynchroniczne. Jedynym problemem może być to, czy twoja pula wątków jest nasycona (tj. Nie ma obecnie dostępnych wątków do obsługi żądania, a ustawienia puli nie pozwalają na utworzenie większej liczby wątków), w którym to przypadku żądanie jest blokowane w oczekiwaniu na uruchomienie zadanie, aż wątek puli stanie się dostępny.

Paul Turner
źródło
Dzięki! Po przeczytaniu Twojej odpowiedzi zredagowałem pytanie za pomocą próbki kodu. Możesz spojrzeć?
tugberk
Masz dla mnie magiczne zdanie: Task.Factory.StartNewcall ustawia kolejkę operacji w puli wątków .NET. . W tym kontekście, który z nich jest poprawny: 1-) Tworzy nowy wątek, a po zakończeniu wątek wraca do puli wątków i czeka na ponowne użycie. 2-) Pobiera wątek z puli wątków i wraca do puli wątków i czeka na ponowne użycie. 3-) Przyjmuje najbardziej efektywne podejście i może zrobić dowolne z nich.
tugberk
1
Pula wątków tworzy wątki, gdy są wymagane, i odtwarza wątki, gdy nie są używane. Dokładne zachowanie różniło się w różnych wersjach środowiska CLR. Szczegółowe informacje na ten temat można znaleźć tutaj msdn.microsoft.com/en-us/library/0ka9477y.aspx
Paul Turner
Teraz zaczyna się to kształtować w mojej głowie. Więc CLR jest właścicielem puli wątków, prawda? Na przykład aplikacja WPF ma również pojęcie puli wątków i zajmuje się również pulą.
tugberk
1
Pula wątków to osobna rzecz w środowisku CLR. Inne komponenty, które są „świadome” puli, wskazują, że używają wątków puli wątków (w stosownych przypadkach), zamiast tworzyć i niszczyć własne. Tworzenie lub niszczenie wątku jest operacją stosunkowo kosztowną, więc korzystanie z puli to duży przyrost wydajności w przypadku krótko działających operacji.
Paul Turner,
2

Tak, używają wątku z puli wątków. W witrynie MSDN istnieje całkiem doskonały przewodnik, który odpowie na wszystkie Twoje pytania i nie tylko. W przeszłości okazało się, że jest to bardzo przydatne. Sprawdź to!

http://msdn.microsoft.com/en-us/library/ee728598.aspx

Tymczasem komentarze + sugestie, które słyszysz o kodzie asynchronicznym, należy traktować z przymrużeniem oka. Na początek samo zrobienie czegoś asynchronicznego niekoniecznie poprawia skalowanie, aw niektórych przypadkach może pogorszyć skalowanie aplikacji. Drugi komentarz, który opublikowałeś na temat „dużego natężenia ruchu ...” również jest poprawny tylko w niektórych kontekstach. To naprawdę zależy od tego, co robią Twoje operacje i jak wchodzą w interakcje z innymi częściami systemu.

Krótko mówiąc, wiele osób ma wiele opinii na temat asynchroniczności, ale mogą one nie być poprawne poza kontekstem. Powiedziałbym, że skup się na swoich konkretnych problemach i wykonaj podstawowe testy wydajności, aby zobaczyć, jakie kontrolery asynchroniczne itp. Faktycznie obsługują twoją aplikację.

AR
źródło
Czytałem ten dokument może setki razy i nadal mam tyle zamieszania (może problem to ja, kto wie). Kiedy się rozejrzysz, zobaczysz tyle niespójnych komentarzy na temat asynchronii w ASP.NET MVC, jak na moje pytanie.
tugberk
dla ostatniego zdania: w ramach akcji kontrolera odpytywałem bazę danych 5 razy osobno (musiałem) i trwało to około 400 ms. Następnie zaimplementowałem AsyncController i uruchomiłem je równolegle. Czas odpowiedzi radykalnie skrócił się do ok. 200 ms. Ale nie mam pojęcia, ile wątków tworzy, co dzieje się z tymi wątkami po GCich zakończeniu , przychodzi i czyści je zaraz po zakończeniu, aby moja aplikacja nie miała wycieku pamięci, i tak dalej. Każdy pomysł w tej sprawie.
tugberk
dołącz debugger i dowiedz się.
AR
0

Po pierwsze, to nie MVC, ale IIS, który utrzymuje pulę wątków. Tak więc każde żądanie przychodzące do aplikacji MVC lub ASP.NET jest obsługiwane z wątków utrzymywanych w puli wątków. Dopiero po utworzeniu aplikacji Asynch wywołuje tę akcję w innym wątku i natychmiast zwalnia wątek, aby można było przyjąć inne żądania.

Wyjaśniłem to samo w szczegółowym filmie ( http://www.youtube.com/watch?v=wvg13n5V0V0/ „Kontrolery MVC Asynch i głód wątków”), który pokazuje, jak głód wątków zachodzi w MVC i jak jest minimalizowany przez użycie MVC Kontrolery asynchroniczne Zmierzyłem również kolejki żądań za pomocą perfmon, dzięki czemu możesz zobaczyć, jak kolejki żądań są zmniejszane dla asynchronizacji MVC i jak najgorsze dla operacji synchronizacji.

Shivprasad Koirala
źródło