Gibt es Fälle, in denen es vorzuziehen ist, ein einfaches altes Thread-Objekt anstelle eines der neueren Konstrukte zu verwenden?

112

Ich sehe viele Leute in Blog-Posts und hier auf SO, die die Verwendung der ThreadKlasse in neueren Versionen von C # entweder vermeiden oder davon abraten (und ich meine natürlich 4.0+, mit dem Zusatz von Task& friends). Schon vorher gab es Debatten darüber, dass die Funktionalität eines einfachen alten Threads in vielen Fällen durch die ThreadPoolKlasse ersetzt werden kann.

Auch andere spezialisierte Mechanismen machen die ThreadKlasse weniger attraktiv, wie z. B. das TimerErsetzen der hässlichen Thread+ SleepKombination, während wir für GUIs BackgroundWorkerusw. haben .

Dennoch Threadscheint das Konzept für einige Leute (mich eingeschlossen) ein sehr vertrautes Konzept zu bleiben, Leute, die, wenn sie mit einer Aufgabe konfrontiert werden, die eine Art parallele Ausführung beinhaltet, direkt zur Verwendung der guten alten ThreadKlasse springen . Ich habe mich in letzter Zeit gefragt, ob es Zeit ist, meine Wege zu ändern.

Meine Frage ist also, gibt es Fälle, in denen es notwendig oder nützlich ist, ein einfaches altes ThreadObjekt anstelle eines der oben genannten Konstrukte zu verwenden?

Tudor
quelle
4
Nicht zu nitpicken (oder vielleicht ... :)), aber das ist .NET (dh nicht auf C # beschränkt).
MetalMikester
11
Bisher ist die durchschnittliche rep für einen Benutzer, der diese Frage beantwortet ist 64.5k. Ziemlich beeindruckend
zzzzBov
1
@zzzzBov: Ich habe darüber nachgedacht, meine eigene Antwort zu veröffentlichen, aber jetzt kann ich nicht aus Angst, den Durchschnitt zu senken :)
Brian Gideon
@BrianGideon, ich sehe keinen Grund, warum das Sie oder andere davon abhalten sollte, diese Frage zu beantworten.
zzzzBov
@ Brian Gideon: Auf jeden Fall bitte posten. :)
Tudor

Antworten:

107

Die Thread-Klasse kann nicht überholt werden, da es sich offensichtlich um ein Implementierungsdetail aller anderen von Ihnen erwähnten Muster handelt.

Aber das ist nicht wirklich deine Frage; Ihre Frage ist

Gibt es Fälle, in denen es notwendig oder nützlich ist, ein einfaches altes Thread-Objekt anstelle eines der oben genannten Konstrukte zu verwenden?

Sicher. In genau den Fällen, in denen eines der übergeordneten Konstrukte Ihren Anforderungen nicht entspricht.

Mein Rat ist, dass Sie, wenn Sie sich in einer Situation befinden, in der vorhandene Tools mit höherer Abstraktion nicht Ihren Anforderungen entsprechen und eine Lösung mithilfe von Threads implementieren möchten , die fehlende Abstraktion identifizieren sollten , die Sie wirklich benötigen , und diese Abstraktion dann implementieren sollten Verwenden Sie Threads und verwenden Sie dann die Abstraktion .

Eric Lippert
quelle
33
Alles gute Ratschläge. Aber haben Sie ein Beispiel, in dem ein einfaches altes Thread-Objekt einem der neueren Konstrukte vorzuziehen ist?
Robert Harvey
Das Debuggen mehrerer Threads mit Zeitproblemen zum Laden von Code kann manchmal viel einfacher sein als das Verwenden anderer Konstrukte auf höherer Ebene. Und Sie benötigen ohnehin die Grundkenntnisse zum Arbeiten und Verwenden von Threads.
CodingBarfield
Danke für die Antwort, Eric. Es ist wirklich leicht zu vergessen, dass unter den jüngsten Bombardierungen über die neuen Threading-Funktionen manchmal der alte Thread noch benötigt wird. Nun, ich denke, Kenntnisse über das Bare-Metal sind sowieso nützlich.
Tudor
15
@ RobertHarvey: Sicher. Wenn Sie beispielsweise eine klassische "Produzent / Verbraucher" -Situation haben, in der ein Thread zum Entfernen von Daten aus einer Arbeitswarteschlange und ein anderer Thread zum Einfügen von Inhalten in eine Arbeitswarteschlange verwendet werden soll. Beide schleifen für immer; Sie lassen sich nicht gut auf Aufgaben abbilden, die Sie für eine Weile ausführen, und erhalten dann ein Ergebnis. Sie benötigen keinen Thread-Pool, da nur zwei Threads vorhanden sind. Und Sie können mit Sperren und Signalen klug umgehen, um sicherzustellen, dass die Warteschlange sicher ist, ohne den anderen Thread für lange Zeit zu blockieren.
Eric Lippert
@Eric: Wird eine solche Abstraktion nicht bereits von TPL Dataflow angeboten ?
Allon Guralnek
52

Threads sind ein grundlegender Baustein für bestimmte Dinge (nämlich Parallelität und Asynchronität) und sollten daher nicht entfernt werden. Allerdings , für die meisten Menschen und die meisten Anwendungsfälle gibt es geeignetere Dinge zu verwenden sind , die Sie, wie Thread - Pools genannt (die parallel eine schöne Art und Weise bieten viele kleine Jobs Handling ohne durch Laichen 2000 Fäden auf einmal Überlastung der Maschine), BackgroundWorker( die nützliche Ereignisse für eine einzelne kurzlebige Arbeit zusammenfasst).

Aber nur weil diese in vielen Fällen besser geeignet sind, da sie den Programmierer davor schützen, das Rad unnötig neu zu erfinden, dumme Fehler zu machen und dergleichen, bedeutet dies nicht, dass die Thread-Klasse veraltet ist. Es wird immer noch von den oben genannten Abstraktionen verwendet, und Sie würden es immer noch benötigen, wenn Sie eine fein abgestimmte Kontrolle über Threads benötigen, die nicht von den spezielleren Klassen abgedeckt werden.

In ähnlicher Weise .NETverbietet es nicht die Verwendung von Arrays, obwohl List<T>es für viele Fälle, in denen Menschen Arrays verwenden, besser geeignet ist. Einfach, weil Sie möglicherweise immer noch Dinge erstellen möchten, die nicht von der Standardbibliothek abgedeckt werden.

Joey
quelle
6
Nur weil ein Automatikgetriebe 99% der Zeit funktioniert, bedeutet dies nicht, dass manuelle Getriebe keinen Wert haben, selbst wenn die Fahrerpräferenz ignoriert wird.
Guvante
4
Ich bin nicht sehr vertraut mit Fahrsprachen, da ich Radfahrer und Benutzer öffentlicher Verkehrsmittel bin. Ein Schaltgetriebe ist jedoch nicht das Herzstück eines Automatikgetriebes - es ist keine mechanische Hand, die einen Steuerknüppel bewegt. Es ist nicht wirklich eine Abstraktion über die andere, soweit ich das sehen kann.
Joey
2
Sie könnten das Wort "Thread" durch "nicht verwalteten Code" im zweiten Absatz ersetzen und es wird immer noch wahr
klingen
1
@Joey: Oh, ich dachte du meinst das Veralterungsstück, den Wert einer feinkörnigen Kontrolle auf Kosten der Benutzerfreundlichkeit, obwohl die meisten es niemals brauchen werden. Übrigens ist technisch gesehen der interessante Teil eines Schaltgetriebes sowieso die Kupplung.
Guvante
3
Wenn sie wirklich mit der Übertragung Metapher gehen wollen, die meisten aufeinanderfolgenden Übertragungen (wie BMW SMG - Getriebe) sind in der Tat, Schaltgetrieben mit einem Computer betätigten Kupplung und Gangwahl. Sie sind keine Planetengetriebe wie herkömmliche Automatiken.
Adam Robinson
13

Taskund Threadsind verschiedene Abstraktionen. Wenn Sie einen Thread modellieren möchten, ist die ThreadKlasse immer noch die am besten geeignete Wahl. Wenn Sie beispielsweise mit dem aktuellen Thread interagieren müssen, sehe ich dafür keine besseren Typen.

Wie Sie jedoch hervorheben, hat .NET mehrere dedizierte Abstraktionen hinzugefügt, die Threadin vielen Fällen vorzuziehen sind.

Brian Rasmussen
quelle
9

Die ThreadKlasse ist nicht veraltet, sie ist unter besonderen Umständen immer noch nützlich.

Wo ich arbeite, haben wir einen 'Hintergrundprozessor' als Teil eines Content-Management-Systems geschrieben: einen Windows-Dienst, der Verzeichnisse, E-Mail-Adressen und RSS-Feeds überwacht und jedes Mal, wenn etwas Neues auftaucht, eine Aufgabe darauf ausführt - normalerweise, um das zu importieren Daten.

Versuche, den Thread-Pool dafür zu verwenden, haben nicht funktioniert: Es wird versucht, zu viel Material gleichzeitig auszuführen und die Datenträger in den Papierkorb zu werfen. Daher haben wir unser eigenes Abfrage- und Ausführungssystem implementiert, indem wir die ThreadKlasse direkt verwendeten .

MiMo
quelle
Ich habe keine Ahnung, ob die Verwendung von Thread direkt hier die richtige Lösung ist, aber +1, um tatsächlich ein Beispiel zu geben, wo ein Wechsel zu Thread erforderlich war.
Oddthinking
6

Die neuen Optionen machen die direkte Verwendung und Verwaltung der (teuren) Threads weniger häufig.

Leute, die, wenn sie mit einer Aufgabe konfrontiert werden, die eine Art parallele Ausführung beinhaltet, direkt zur Verwendung der guten alten Thread-Klasse springen.

Das ist eine sehr teure und relativ komplexe Art, Dinge parallel zu machen.

Beachten Sie, dass die Kosten am wichtigsten sind: Sie können keinen vollständigen Thread für kleine Aufgaben verwenden, dies wäre kontraproduktiv. Der ThreadPool bekämpft die Kosten, die Task-Klasse die Komplexität (Ausnahmen, Warten und Abbrechen).

Henk Holterman
quelle
5

Um die Frage zu beantworten: " Gibt es Fälle, in denen es notwendig oder nützlich ist, ein einfaches altes Thread-Objekt zu verwenden?" ", Würde ich sagen, dass ein einfacher alter Thread nützlich (aber nicht notwendig) ist, wenn Sie einen lang laufenden Prozess haben, den Sie gewonnen haben. nie mit einem anderen Thread interagieren.

Wenn Sie beispielsweise eine Anwendung schreiben, die Nachrichten aus einer Nachrichtenwarteschlange abonniert, und Ihre Anwendung mehr als nur diese Nachrichten verarbeitet, ist es hilfreich, einen Thread zu verwenden, da dies der Thread ist in sich geschlossen (dh Sie warten nicht darauf, dass es erledigt wird), und es ist nicht von kurzer Dauer. Die Verwendung der ThreadPool-Klasse dient eher dazu, eine Reihe von kurzlebigen Arbeitselementen in die Warteschlange zu stellen und der ThreadPool-Klasse die effiziente Verarbeitung jedes einzelnen zu ermöglichen, sobald ein neuer Thread verfügbar ist. Aufgaben können verwendet werden, wenn Sie Thread direkt verwenden würden, aber im obigen Szenario glaube ich nicht, dass sie Ihnen viel kosten würden. Sie helfen Ihnen dabei, einfacher mit dem Thread zu interagieren (was im obigen Szenario nicht der Fall ist).

Derek Greer
quelle
Ich bin damit einverstanden, dass die meisten coolen neuen Parallelitäts-Dinge auf kurzfristige kleine feinkörnige Aufgaben ausgelegt sind und lang laufende Threads weiterhin mit implementiert werden sollten System.IO.Threading.Thread.
Ben Voigt
4

Wahrscheinlich nicht die Antwort, die Sie erwartet hatten, aber ich verwende Thread die ganze Zeit beim Codieren gegen .NET Micro Framework. MF ist ziemlich reduziert und enthält keine Abstraktionen auf höherer Ebene. Die Thread-Klasse ist sehr flexibel, wenn Sie das letzte Stück Leistung aus einer Low-MHz-CPU herausholen möchten.

AngryHacker
quelle
Hey, das ist eigentlich ein ziemlich gutes Beispiel! Vielen Dank. Ich habe nicht an Frameworks gedacht, die die neuen Funktionen nicht unterstützen.
Tudor
3

Sie können die Thread-Klasse mit ADO.NET vergleichen. Es ist nicht das empfohlene Tool, um die Arbeit zu erledigen, aber es ist nicht veraltet. Andere Tools bauen darauf auf, um die Arbeit zu erleichtern.

Es ist nicht falsch, die Thread-Klasse über andere Dinge zu verwenden, insbesondere wenn diese Dinge keine Funktionalität bieten, die Sie benötigen.

Kyle Trauberman
quelle
3

Es ist nicht definitiv veraltet.

Das Problem bei Multithread-Apps besteht darin, dass es sehr schwierig ist, sie richtig zu machen (oft ist unbestimmtes Verhalten, Eingabe, Ausgabe und auch der interne Status wichtig). Daher sollte ein Programmierer so viel Arbeit wie möglich auf Framework / Tools übertragen. Abstrakt es weg. Aber der Todfeind der Abstraktion ist die Leistung.

Meine Frage ist also, gibt es Fälle, in denen es notwendig oder nützlich ist, ein einfaches altes Thread-Objekt anstelle eines der oben genannten Konstrukte zu verwenden?

Ich würde mich nur für Threads und Sperren entscheiden, wenn es ernsthafte Leistungsprobleme und Hochleistungsziele gibt.

Lukasz Madon
quelle
3

Ich habe immer die Thread-Klasse verwendet, wenn ich die Anzahl der Threads, die ich hochgefahren habe, zählen und kontrollieren muss. Mir ist klar, dass ich den Threadpool verwenden könnte, um alle meine herausragenden Arbeiten zu speichern, aber ich habe nie einen guten Weg gefunden, um zu verfolgen, wie viel Arbeit gerade erledigt wird oder wie der Status ist.

Stattdessen erstelle ich eine Sammlung und platziere die Threads in ihnen, nachdem ich sie hochgefahren habe. Das allerletzte, was ein Thread tut, ist, sich selbst aus der Sammlung zu entfernen. Auf diese Weise kann ich immer feststellen, wie viele Threads ausgeführt werden, und ich kann die Sammlung verwenden, um jeden zu fragen, was er tut. Wenn es einen Fall gibt, in dem ich sie alle töten muss, müssen Sie normalerweise eine Art "Abbruch" -Flag in Ihrer Anwendung setzen. Warten Sie, bis jeder Thread dies für sich bemerkt und sich selbst beendet - in meinem Fall ich kann durch die Sammlung gehen und nacheinander einen Thread.Abort für jeden ausgeben.

In diesem Fall habe ich keinen besseren Weg gefunden, als direkt mit der Thread-Klasse zu arbeiten. Wie Eric Lippert erwähnte, handelt es sich bei den anderen nur um Abstraktionen auf höherer Ebene, und es ist angebracht, mit den Klassen auf niedrigerer Ebene zu arbeiten, wenn die verfügbaren Implementierungen auf höherer Ebene nicht Ihren Anforderungen entsprechen. So wie Sie manchmal Win32-API-Aufrufe ausführen müssen, wenn .NET nicht genau Ihren Anforderungen entspricht, wird es immer Fälle geben, in denen die Thread-Klasse trotz der jüngsten "Fortschritte" die beste Wahl ist.

SqlRyan
quelle