Czy są jakieś przypadki, w których lepiej jest użyć zwykłego starego obiektu Thread zamiast jednej z nowszych konstrukcji?


112

Widzę wiele osób w postach na blogach i tutaj na SO, które unikają lub odradzają używanie tej Threadklasy w najnowszych wersjach C # (i mam na myśli oczywiście 4.0+, z dodatkiem Task& friends). Jeszcze wcześniej toczyły się dyskusje na temat tego, że zwykły stary wątek może w wielu przypadkach zostać zastąpiony przez ThreadPoolklasę.

Ponadto inne wyspecjalizowane mechanizmy dodatkowo sprawiają, że Threadklasa jest mniej atrakcyjna, na przykład Timerzastępując brzydką kombinację Thread+ Sleep, podczas gdy w przypadku GUI, które mamy BackgroundWorker, itp.

Mimo to Threadwydaje się, że koncepcja pozostaje bardzo dobrze znana niektórym ludziom (w tym mnie), ludziom, którzy w obliczu zadania wymagającego pewnego rodzaju równoległej realizacji przechodzą bezpośrednio do używania starego, dobregoThread klasy. Zastanawiałem się ostatnio, czy czas zmienić swoje postępowanie.

Więc moje pytanie brzmi: czy są jakieś przypadki, w których konieczne lub przydatne jest użycie zwykłego starego Threadobiektu zamiast jednej z powyższych konstrukcji?


4
Nie po to, żeby się dziwić (a może ... :)), ale to jest .NET (czyli nie tylko C #).
MetalMikester

11
Jak dotąd średni przedstawiciel użytkownika, który odpowiedział na to pytanie, wynosi 64.5k. Całkiem imponujący pokaz
zzzzBov

1
@zzzzBov: Myślałem o zamieszczeniu własnej odpowiedzi, ale teraz nie mogę się obawiać obniżenia średniej :)
Brian Gideon

@BrianGideon, nie widzę powodu, dla którego miałoby to powstrzymywać Ciebie lub kogokolwiek przed udzieleniem odpowiedzi na to pytanie.
zzzzBov

@Brian Gideon: Jak najbardziej, napisz. :)
Tudor

Odpowiedzi:


107

Klasa Thread nie może stać się przestarzała, ponieważ oczywiście jest to szczegół implementacji wszystkich innych wzorców, o których wspominasz.

Ale to naprawdę nie jest twoje pytanie; Twoje pytanie brzmi

czy są jakieś przypadki, w których konieczne lub przydatne jest użycie zwykłego starego obiektu Thread zamiast jednej z powyższych konstrukcji?

Pewnie. Dokładnie w tych przypadkach, w których jedna z konstrukcji wyższego poziomu nie spełnia twoich potrzeb.

Moja rada jest taka, że ​​jeśli znajdziesz się w sytuacji, w której istniejące narzędzia o wyższej abstrakcji nie spełniają Twoich potrzeb i chcesz wdrożyć rozwiązanie za pomocą wątków, to powinieneś zidentyfikować brakującą abstrakcję, której naprawdę potrzebujesz , a następnie wdrożyć tę abstrakcję używając wątków , a następnie użyj abstrakcji .


33
Dobra rada. Ale czy masz przykład, w którym zwykły stary obiekt wątku może być lepszy od jednej z nowszych konstrukcji?
Robert Harvey

Debugowanie wielu wątków z problemami z synchronizacją w celu załadowania kodu może być czasami znacznie łatwiejsze niż przy użyciu innych konstrukcji „wyższego” poziomu. I tak potrzebujesz podstawowej wiedzy na temat pracy i używania wątków.
CodingBarfield

Dzięki za odpowiedź, Eric. Naprawdę łatwo jest zapomnieć, podczas niedawnego bombardowania nowych funkcji wątków, że czasami stary wątek jest nadal potrzebny. Cóż, wydaje mi się, że znajomość gołego metalu i tak jest przydatna.
Tudor

15
@RobertHarvey: Jasne. Na przykład, jeśli masz klasyczną sytuację „producent / konsument”, w której chcesz mieć jeden wątek przeznaczony do pobierania rzeczy z kolejki roboczej, a drugi do umieszczania rzeczy w kolejce roboczej. Obie pętle na zawsze; nie odwzorowują ładnie zadań, które wykonujesz przez jakiś czas, a następnie otrzymujesz wynik. Nie potrzebujesz puli wątków, ponieważ są tylko dwa wątki. Możesz też być sprytny, używając blokad i sygnałów, aby zapewnić, że kolejka jest bezpieczna bez blokowania drugiego wątku przez długi czas.
Eric Lippert

@Eric: Czy TPL Dataflow nie oferuje już takiej abstrakcji ?
Allon Guralnek

52

Wątki są podstawowym blokiem konstrukcyjnym niektórych rzeczy (mianowicie równoległości i asynchronii) i dlatego nie powinny być usuwane. Jednak dla większości ludzi i większości przypadków użycia są bardziej odpowiednie rzeczy do użycia, o których wspomniałeś, takie jak pule wątków (które zapewniają przyjemny sposób obsługi wielu małych zadań równolegle bez przeciążania maszyny przez tworzenie 2000 wątków naraz), BackgroundWorker( który zawiera przydatne zdarzenia dla pojedynczego krótkotrwałego fragmentu pracy).

Ale tylko dlatego, że w wielu przypadkach są one bardziej odpowiednie, ponieważ chronią programistę przed niepotrzebnym wymyślaniem koła na nowo, popełnianiem głupich błędów i tym podobnych, nie oznacza to, że klasa Thread jest przestarzała. Jest nadal używany przez powyższe abstrakcje i nadal będziesz go potrzebować, jeśli potrzebujesz precyzyjnej kontroli nad wątkami, która nie jest objęta bardziej specjalnymi klasami.

W podobny .NETsposób nie zabrania używania tablic, mimo List<T>że lepiej pasuje do wielu przypadków, w których ludzie używają tablic. Po prostu dlatego, że nadal możesz chcieć tworzyć rzeczy, które nie są objęte standardową biblioteką.


6
Tylko dlatego, że automatyczna skrzynia biegów działa przez 99% czasu, nie oznacza, że ​​ręczne skrzynie biegów nie mają wartości, nawet ignorując preferencje kierowcy.
Guvante

4
Niezbyt dobrze znam idiomy związane z prowadzeniem pojazdu, biorąc pod uwagę, że jestem rowerzystą i użytkownikiem transportu publicznego. Ale manualna skrzynia biegów nie jest sercem automatycznej - to nie jest mechaniczna ręka poruszająca drążkiem. Tak naprawdę nie jest to abstrakcja w stosunku do innych, o ile to widzę.
Joey

2
Można zastąpić słowo „GW” z „niekontrolowana kodu” w akapicie drugim i będzie nadal brzmią prawdziwie
Conrad Frix

1
@Joey: Oh, myślałem, że masz na myśli przestarzały element, wartość precyzyjnej kontroli kosztem użyteczności, mimo że większość nigdy jej nie będzie potrzebować. Przy okazji technicznie interesującą częścią manualnej skrzyni biegów i tak jest sprzęgło.
Guvante

3
Jeśli naprawdę chcesz iść za metaforą skrzyni biegów, większość przekładni sekwencyjnych (takich jak skrzynia biegów BMW SMG) to w rzeczywistości ręczne skrzynie biegów ze sprzęgłem sterowanym komputerowo i dźwignią zmiany biegów. Nie są to przekładnie planetarne, jak konwencjonalna automatyka.
Adam Robinson

13

Taski Threadsą różnymi abstrakcjami. Jeśli chcesz modelować wątek, Threadklasa jest nadal najbardziej odpowiednim wyborem. Np. Jeśli potrzebujesz wejść w interakcję z bieżącym wątkiem, nie widzę do tego lepszych typów.

Jednak, jak zauważyłeś .NET dodał kilka dedykowanych abstrakcji, które Threadw wielu przypadkach są lepsze .


9

ThreadKlasa nie jest przestarzały, jest nadal przydatna w szczególnych okolicznościach.

Tam, gdzie pracuję, napisaliśmy `` procesor w tle '' jako część systemu zarządzania treścią: usługa Windows, która monitoruje katalogi, adresy e-mail i kanały RSS, i za każdym razem, gdy pojawi się coś nowego, wykonaj na nim zadanie - zwykle w celu zaimportowania dane.

Próby wykorzystania w tym celu puli wątków nie powiodły się: próbuje on wykonać zbyt dużo rzeczy w tym samym czasie i wyrzucić dyski, więc zaimplementowaliśmy własny system sondowania i wykonywania, używając bezpośrednio tej Threadklasy.


Nie mam pojęcia, czy użycie wątku bezpośrednio tutaj jest tutaj właściwym rozwiązaniem, ale +1 za podanie przykładu, gdzie było wymagane przejście do wątku.
Dziwne myślenie

6

Nowe opcje sprawiają, że bezpośrednie użycie i zarządzanie (drogimi) wątkami jest rzadsze.

ludzie, którzy w obliczu zadania wymagającego pewnego rodzaju równoległego wykonywania przechodzą bezpośrednio do używania starej, dobrej klasy Thread.

To bardzo kosztowny i stosunkowo złożony sposób robienia rzeczy równolegle.

Zauważ, że koszt ma największe znaczenie: nie możesz użyć pełnego wątku do wykonania małej pracy, przyniosłoby to efekt przeciwny do zamierzonego. ThreadPool zwalcza koszty, klasa Task - złożoność (wyjątki, oczekiwanie i anulowanie).


5

Aby odpowiedzieć na pytanie „czy są jakieś przypadki, w których konieczne lub przydatne jest użycie zwykłego, starego obiektu Thread? ”, powiedziałbym, że zwykły stary wątek jest przydatny (ale niekonieczny) w przypadku długotrwałego procesu, który wygrałeś ” nigdy nie wchodzisz w interakcję z innego wątku.

Na przykład, jeśli piszesz aplikację, która subskrybuje odbieranie wiadomości z jakiejś kolejki wiadomości, a Twoja aplikacja będzie robić więcej niż tylko przetwarzać te wiadomości, przydatne byłoby użycie wątku, ponieważ wątek będzie samowystarczalny (tj. nie czekasz, aż to zrobisz) i nie jest krótkotrwały. Używanie klasy ThreadPool służy raczej do kolejkowania kilku krótkotrwałych elementów roboczych i umożliwiania klasie ThreadPool wydajne przetwarzanie każdego z nich, gdy dostępny jest nowy wątek. Zadania mogą być używane tam, gdzie używałbyś bezpośrednio Thread, ale w powyższym scenariuszu nie sądzę, aby kupili ci dużo. Pomagają ci w łatwiejszej interakcji z wątkiem (czego nie robi powyższy scenariusz


Zgadzam się, że większość fajnych nowych rzeczy związanych z równoległością jest zaprojektowana wokół krótkoterminowych małych, drobnoziarnistych zadań, a długotrwałe wątki nadal powinny być implementowane z System.IO.Threading.Thread.
Ben Voigt,

4

Prawdopodobnie nie jest to odpowiedź, której się spodziewałeś, ale używam Thread przez cały czas, gdy koduję na platformie .NET Micro Framework. MF jest dość ograniczony i nie zawiera abstrakcji wyższego poziomu, a klasa Thread jest bardzo elastyczna, gdy potrzebujesz uzyskać ostatni bit wydajności z procesora o niskim MHz.


Hej, to całkiem dobry przykład! Dzięki. Nie myślałem o frameworkach, które nie obsługują nowych funkcji.
Tudor

3

Możesz porównać klasę Thread z ADO.NET. Nie jest to zalecane narzędzie do wykonania pracy, ale nie jest przestarzałe. Oprócz tego wbudowane są inne narzędzia ułatwiające pracę.

Używanie klasy Thread do innych rzeczy nie jest złe, zwłaszcza jeśli nie zapewniają one potrzebnej funkcjonalności.


3

Nie jest to zdecydowanie przestarzałe.

Problem z aplikacjami wielowątkowymi polega na tym, że są one bardzo trudne do uzyskania (często nieokreślone zachowanie, dane wejściowe, dane wyjściowe, a także stan wewnętrzny są ważne), więc programista powinien włożyć jak najwięcej pracy do frameworka / narzędzi. Odebrać to. Ale śmiertelnym wrogiem abstrakcji jest wydajność.

Więc moje pytanie brzmi: czy są jakieś przypadki, w których konieczne lub przydatne jest użycie zwykłego starego obiektu Thread zamiast jednej z powyższych konstrukcji?

Postawiłbym na wątki i blokady tylko wtedy, gdy wystąpią poważne problemy z wydajnością, cele związane z wysoką wydajnością.


3

Zawsze korzystałem z klasy Thread, gdy muszę liczyć i kontrolować zwinięte wątki. Zdaję sobie sprawę, że mógłbym wykorzystać pulę wątków do przechowywania wszystkich moich wybitnych prac, ale nigdy nie znalazłem dobrego sposobu na śledzenie, ile pracy jest obecnie wykonywanych lub jaki jest stan.

Zamiast tego tworzę kolekcję i umieszczam w niej wątki po ich podkręceniu - ostatnią rzeczą, jaką robi, jest usunięcie się z kolekcji. W ten sposób zawsze mogę stwierdzić, ile wątków jest uruchomionych, i mogę użyć kolekcji, aby zapytać każdy, co robi. Jeśli jest przypadek, gdy muszę je wszystkie zabić, normalnie musiałbyś ustawić jakąś flagę „Przerwij” w swojej aplikacji, poczekaj, aż każdy wątek zauważy to samodzielnie i sam się zakończy - w moim przypadku, ja może przejść się po kolekcję i wydać wątek, po kolei każdemu z nich.

W takim przypadku nie znalazłem lepszego sposobu niż praca bezpośrednio z klasą Thread. Jak wspomniał Eric Lippert, pozostałe są po prostu abstrakcjami wyższego poziomu i dobrze jest pracować z klasami niższego poziomu, gdy dostępne implementacje wysokiego poziomu nie spełniają twoich potrzeb. Tak jak czasami trzeba wykonywać wywołania Win32 API, gdy .NET nie odpowiada dokładnie Twoim potrzebom, zawsze będą przypadki, w których klasa Thread będzie najlepszym wyborem, pomimo ostatnich „ulepszeń”.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.