Pytanie Czy zasoby wątku zadań .NET wracają do puli tymczasowo, jeśli wątek czeka na operację asynchroniczną, aby zakończyć?


Mam zadanie TPL, które wykonuje dwie rzeczy. Najpierw wywołuje usługę sieciową. Po drugie, wstawia pewne dane do bazy danych. Mam do 20 zadań rozpoczętych w tym samym czasie, robiąc to samo w kółko. Wszystko, co robią przez cały dzień, to wywoływanie usług sieciowych i wstawianie danych do bazy danych.

Jestem całkiem nowy na TPL w .NET. Zrobiłem pewne rzeczy z procesami działającymi w tle i asynchronizującymi usługami sieciowymi.

Wywołanie usługi WWW i wstawienie bazy danych blokują wywołania w wątku, w którym działa zadanie.

Rozumiem, że pod pokrywami, podczas korzystania z zadań, .NET zarządza pulą wątków. Tak?

Czy pula wątków ma więcej wątków do dyspozycji, jeśli wykonam wywołanie usługi i wywołanie bazy danych za pomocą async i czekam () zamiast blokowania wywołań?

Moja teoria (i nie jestem pewna, dlaczego tak myślę) jest taka, że ​​wątek nie robi nic, nie czekając na blokującą się usługę sieciową i nie może tymczasowo zwrócić zasobów do puli. Zastanawiam się jednak, czy zadania czekały na wywołania asynchroniczne, aby zakończyć, czy główny wątek zadań będzie w stanie przełączyć się, aby inne procesy mogły działać podczas oczekiwania.

Czy moja teoria jest właściwa? Czy robię rzeczy?

Używam c # i .NET 4.0, ale w razie potrzeby mogę przejść do 4.5.


11
2017-08-15 07:43


pochodzenie


Masz rację, że twoja obecna implementacja nie jest optymalna (może być wystarczająco dobra, więc zmieniasz ją tylko wtedy, gdy widzisz jakiś problem). Jednak aby odpowiedzieć na twoje drugie pytanie, musisz podać przykład użycia asynchronizmu w zadaniu. Teoretycznie wątek zostanie przywrócony do puli, ale istnieje wiele sposobów, aby go naprawić. - Stilgar
Dziękuję Stilgar. Teraz idę do łóżka. 1:00 czasu Seattle. Ale jutro opublikuję więcej kodu, a następnie usuń ten komentarz, gdy już to zrobię. - Trevor
@Trevor: Myślę, że kluczową rzeczą, o której należy pamiętać, są dwa rodzaje zadań. Nazywam je Zadaniami Obietnicy i Zadaniami Delegata. Tylko zadania delegowania są kiedykolwiek wykonywane w wątku; Obietnice Zadania są po prostu abstrakcyjną reprezentacją jakiegoś przyszłego wydarzenia. TPL używa zadań delegowania prawie wyłącznie; async/await używa zadań Promise prawie wyłącznie. Sprawdzić mój wpis na blogu po więcej informacji. - Stephen Cleary


Odpowiedzi:


Czy puli wątków ma więcej wątków do dyspozycji, jeśli zrobię   wywołanie usługi i wywołanie bazy danych za pomocą async i aait () zamiast   sprawiając, że blokują połączenia?

To zależy od tego, o co ci chodzi "korzystanie z async-await".

Kiedy używasz Task.Runza kulisami Task klasa używa ThreadPool odciążyć pracę za pomocą ThreadPool wątek.

Jeśli twoja usługa nie ujawnia prawdziwego apiksu async i używasz Task.Run aby umieścić w kolejce swoją pracę, nadal będziesz blokować wątek z wątkami, aby wykonywać prace związane z IO, bez względu na użycie async-await. W swoim pytaniu stwierdzasz, że oba połączenia blokują połączenia, i w takim przypadku odpowiedź brzmi "nie", wątek wątku używany do wywoływania tych blokad nadal jest blokowany.

Jeśli twoje połączenia z usługami i bazami danych są prawdziwe asynchroniczne interfejsy API (takie, które nie zużywają dodatkowych wątków do wykonywania swojej pracy), możesz skorzystać z zalet async-await, jak wtedy, gdy ty await na jednym z tych wywołań (i nie powinieneś potrzebować używać Task.Run z nimi w ogóle), bieżący wątek przyniesie kontrolę z powrotem do dzwoniącego, i może być użyty w międzyczasie do zrobienia więcej pracy. Jeśli tak, to tak.

Moja teoria (i nie jestem pewna, dlaczego tak myślę) jest taka, że ​​wątek nie robi nic, nie czekając na blokującą się usługę sieciową i nie może tymczasowo zwrócić zasobów do puli. Zastanawiam się jednak, czy zadania czekały na wywołania asynchroniczne, aby zakończyć, czy główny wątek zadań będzie w stanie przełączyć się, aby inne procesy mogły działać podczas oczekiwania.

Twoja teoria jest poprawna. Jeśli głównym zadaniem kolejki wątków jest wykonanie żądania związanego z IO, to większość czasu spędzonego na jego przechowywaniu jest po prostu blokowana do momentu zakończenia żądania.

Kiedy ty await za Task, kontrola zwraca się dzwoniącemu. Załóżmy, że Twoje zgłoszenie serwisowe było wywołaniem REST, którego możesz użyć HttpClient który eksponuje prawdziwe asynchroniczne metody asynchroniczne, takie jak GetAsync, PostAsynci kiedy ty awaitte połączenia, twój wątek wywoływania jest zwolniony, aby zrobić więcej pracy w międzyczasie.


7
2017-08-15 07:53



Kiedy mówisz, że praca związana z IO wiąże się z wstawkami bazy danych? - Trevor
Tak, wywołania bazy danych są powiązane z IO. Większość ORM przedstawia dziś asynchroniczne punkty końcowe (EF6, ADO.NET) - Yuval Itzchakov


Jeśli wszystkie zadania aplikacji zostaną zablokowane, każde zadanie użyje wątku z puli wątków.

Jeśli wszystkie zadania regularnie await, Pula wątków nie musi używać wątku dla każdego zadania.

Kiedy twój kod awaits Operacja, która nie została jeszcze zakończona, stan metody jest zapisywany w taki sposób, że można go wznowić w dowolnym innym wątku.

Bezczynne wątki threadpool zostają zwolnione po pewnym czasie, więc wątek, który trafi an await może zostać zwolniony z puli wątków podczas wywoływania metody await nadal działa.

Łącząc to wszystko, asynchroniczna wersja rutyny może wykonać tę samą pracę z mniejszą ilością wątków (zakładając, że obciążenie wystarcza na zrównoważenie czasu oczekiwania na wirowanie CPU).

Ten kod uruchamia 100 zadań powodujących synchroniczne oczekiwanie:

var numTasks = 100;
for (int i = 0; i < numTasks; i++)
{
    Thread.Sleep(5);
    Task.Run(() =>
    {
        Thread.Sleep(5000);
        Interlocked.Decrement(ref numTasks);
    });
}
while (numTasks > 0) Thread.Sleep(100);

W przypadku asynchronicznego oczekiwania zmień je na:

    Task.Run(async () =>
    {
        await Task.Delay(5000);
        Interlocked.Decrement(ref numTasks);
    });

W moim systemie wersja asynchroniczna zwiększa maksymalną liczbę wątków o połowę i zajmuje 20% czasu na wykonanie tej samej "pracy".


2
2017-08-15 12:05





Odpowiedź brzmi tak. Chociaż technicznie nie jest to "oczekiwanie" na zakończenie operacji asynchronicznej (w przeciwnym razie nie byłoby korzyści asynchronicznej). Pod maską znajduje się delegat wywołania zwrotnego, który jest uruchamiany po zakończeniu operacji asynchronicznej, co umożliwia kontynuowanie wątku wywoływania bez blokowania. To magia asynchroniczna / oczekująca zamienia te "kontynuacje" w linearnie wyglądający fragment kodu.

Ponieważ używasz wątku z wątkami, gdy pojawi się oczekiwanie, wątek wróci do wątku. Rzeczą, którą należy zachować ostrożność jest to, że normalne zachowanie następuje po zakończeniu oczekiwanej operacji, a następnie spróbuje powrócić do wątku, w którym zostało uruchomione (teraz prawdopodobnie jest używane przez inne zadanie), aby można było zaobserwować problemy z opóźnieniem w uzyskaniu wyniki z powrotem, gdy wątki threadpool są teraz powiązane, rozpoczynając inne zadania. Z biegiem czasu, wątek będzie próbował dostosować liczbę dostępnych wątków, aby zaspokoić popyt, ale może się okazać, że to nie nastąpi wystarczająco szybko, jeśli pracujesz w seriach. Wynik będzie najwyraźniej kiepski, ponieważ możesz mieć tylko niewielką liczbę dostępnych wątków.


0
2017-08-15 13:24