在任何情况下,最好使用普通的旧Thread对象而不使用较新的结构之一吗?


112

我在博客文章和SO上看到了很多人,他们避免或建议不要Thread在最新版本的C#中使用该类(我的意思是当然是4.0+,加上Task&朋友)。甚至在以前,就存在这样的争论:在许多情况下,ThreadPool该类可以替换普通的旧线程的功能。

另外,其他专用机制进一步使Thread该类不再那么吸引人,例如Timers替换了丑陋的Thread+ Sleep组合,而对于GUI我们拥有BackgroundWorker,等等。

但是,Thread对于某些人(包括我自己)来说,这似乎仍然是一个非常熟悉的概念,这些人在遇到涉及某种并行执行的任务时,会直接跳到使用旧的Thread类。我最近一直在想是否应该修改我的方式。

所以我的问题是,在任何情况下有必要使用普通的旧Thread对象代替上述构造之一是有必要的还是有用的?


4
不用挑剔(或者也许... :)),但这就是.NET(即不限于C#)。
MetalMikester,2012年

11
到目前为止,回答此问题的用户的平均代表为64.5k。令人印象深刻的展示
zzzzBov 2012年

1
@zzzzBov:我当时正在考虑发布自己的答案,但现在我不必担心会降低平均值:)
Brian Gideon 2012年

@BrianGideon,我认为没有理由阻止您或其他任何人回答这个问题。
zzzzBov 2012年

@Brian Gideon:一定要张贴。:)
Tudor 2012年

Answers:


107

不能使Thread类过时,因为显然它是您提到的所有其他模式的实现细节

但这不是你真正的问题;你的问题是

在任何情况下有必要使用普通的旧Thread对象而不是上述结构之一是有必要的还是有用的?

当然。正是在其中较高级别的构造之一不能满足您的需求的情况下。

我的建议是,如果您发现现有的高抽象工具无法满足您的需求,并且希望使用线程来实现解决方案,那么您应该确定您真正需要的缺失抽象,然后实现该抽象使用线程,然后使用抽象


33
所有好的建议。但是,您是否有一个示例,其中普通的旧线程对象可能比较新的结构之一更可取?
罗伯特·哈维

与使用其他“更高”级别的结构相比,有时可以更容易地完成具有时序问题的多个线程的调试以加载一些代码。而且您仍然需要使用和使用线程的基础知识。
CodingBarfield 2012年

谢谢您的回答,埃里克。真的很容易忘记,在最近对新线程功能的轰炸中,有时仍然需要旧线程。好吧,我想无论如何都要了解裸机知识。
2012年

15
@RobertHarvey:好的。例如,如果您遇到典型的“生产者/消费者”情况,则希望有一个线程专用于将工作内容从工作队列中取出,而另一个线程专用于将工作人员放入工作队列中。两者永远循环;它们不能很好地映射到您一段时间执行的任务,然后获得结果。您不需要线程池,因为只有两个线程。而且,您可以巧妙地使用锁和信号来确保队列保持安全,而不会长时间阻塞另一个线程。
埃里克·利珀特

@Eric:TPL Dataflow是否已经提供了这种抽象?
Allon Guralnek

52

线程是某些事物(即并行性和异步性)的基本构建块,因此不应被抢走。但是,对于大多数人和大多数用例来说,您提到的是要使用的更合适的东西,例如线程池(线程池提供了一种不错的方式来并行处理许多小型作业,而不会一次产生2000个线程而使机器不过载),BackgroundWorker(它封装了单个短暂工作的有用事件)。

但是,因为在许多情况下它们更合适,因为它们可以保护程序员避免不必要地重新发明轮子,犯愚蠢的错误等,但这并不意味着Thread类已过时。上面命名的抽象仍然使用它,如果您需要对线程进行更细粒度的控制,而这些特殊类并未涵盖,则仍然需要它。

同样.NET,尽管List<T>它更适合人们使用数组的许多情况,但并不禁止使用数组。仅仅是因为您可能仍想构建标准库未涵盖的内容。


6
仅仅因为自动变速箱在99%的时间内都能正常工作,并不意味着手动变速箱没有价值,即使忽略驾驶员的喜好。
古凡特

4
鉴于我是骑自行车的人和公共交通工具的使用者,对驾车习惯不是很熟悉。但是,手动变速箱不是自动变速箱的核心-它不是机械操纵杆。就我所知,这并不是对另一个的抽象。
乔伊

2
你可以在第二段替换为“非托管代码”一字“线”,它仍然会不顺
康拉德Frix

1
@Joey:哦,我以为您的意思是过时的产品,即具有细粒度控制的价值,但是却以可用性为代价,即使大多数人永远都不需要它。顺便说一句,从技术上讲,手动变速器最有趣的部分是离合器。
Guvante 2012年

3
如果你们真的想要去与传输的比喻,最连续传输(如宝马的SMG变速箱),其实用电脑手动变速器操作离合器和选档杆。它们不是传统自动变速器那样的行星齿轮系统。
亚当·罗宾逊

13

TaskThread是不同的抽象。如果要对线程建模,则Thread类仍然是最合适的选择。例如,如果您需要与当前线程进行交互,那么我看不到任何更好的类型。

但是,正如您所指出的,.NET添加了一些专用抽象,Thread在许多情况下,这些抽象是更可取的。


9

Thread班是不是过时了,它仍然是在特殊情况下非常有用。

在我工作的地方,我们写了一个“后台处理器”作为内容管理系统的一部分:一个Windows服务,该服务监视目录,电子邮件地址和RSS feed,并且每当出现新内容时,都会在其上执行任务-通常是导入数据。

尝试使用线程池的尝试没有用:它试图同时执行过多的工作并浪费磁盘,因此我们直接使用Thread该类实现了自己的轮询和执行系统。


我不知道这里是否直接使用Thread是正确的解决方案,但是+1实际给出了需要使用Thread的示例。
2012年

6

新选项使直接使用和管理(昂贵的)线程的频率降低。

人们在遇到涉及某种并行执行的任务时,会直接跳到使用旧的Thread类。

这是一种非常昂贵且相对复杂的并行处理方式。

请注意,开销最重要:您不能使用全线程来完成一项小工作,这会适得其反。ThreadPool消除了成本,Task类复杂了(异常,等待和取消)。


5

要回答“”问题,是否有必要使用普通的旧Thread对象? ”的问题,我想说一个普通的旧线程在您需要长时间运行的过程时很有用(但不是必需的)。永远不会从其他线程进行交互。

例如,如果您正在编写一个应用程序,该应用程序订阅从某种消息队列中接收消息,并且您的应用程序将要做的不仅仅是处理这些消息,那么使用线程将很有用,因为该线程将独立的(即您不必等待完成),而且它不是短暂的。使用ThreadPool类的目的更多是为了排队一堆短暂的工作项,并允许ThreadPool类在有新的线程可用时有效地处理每个工作项。可以在直接使用Thread的地方使用任务,但是在上述情况下,我认为它们不会给您带来很多好处。它们可以帮助您更轻松地与线程进行交互(上述情况不可以)


我同意,大多数很棒的并行新事物都是围绕短期小型细粒度任务设计的,而长期运行的线程仍应使用来实现System.IO.Threading.Thread
Ben Voigt 2012年

4

可能不是您期望的答案,但是在针对.NET Micro Framework进行编码时,我一直都使用Thread。当您需要从低MHz的CPU中获得最后的性能时,MF的功能已大大减少,并且不包含更高级别的抽象,并且Thread类非常灵活。


嘿,这实际上是一个很好的例子!谢谢。我没有考虑过不支持新功能的框架。
2012年

3

您可以将Thread类与ADO.NET进行比较。它不是推荐的完成工作的工具,但它不是过时的。其他工具可基于此工具来简化工作。

在其他事物上使用Thread类是没有错的,尤其是在那些事物不能提供所需功能的情况下。


3

它不是绝对过时的。

多线程应用程序的问题在于它们很难正确处理(通常不确定的行为,输入,输出以及内部状态也很重要),因此程序员应将尽可能多的工作推向框架/工具。摘掉它。但是,抽象的致命敌人是性能。

所以我的问题是,是否有必要使用普通的旧Thread对象而不是上述结构之一的情况?

只有在存在严重的性能问题和高性能目标时,我才会使用线程和锁。


3

当我需要保持计数和控制自己创建的线程时,我总是使用Thread类。我意识到我可以使用线程池来保存所有出色的工作,但是我从来没有找到一种好的方法来跟踪当前正在执行的工作量或状态。

相反,我创建了一个集合,并在旋转它们后将线程放入其中-线程所做的最后一件事是将自己从该集合中删除。这样,我总是可以知道正在运行多少个线程,并且可以使用集合来询问每个线程在做什么。如果在某些情况下我需要全部杀死它们,通常必须在应用程序中设置某种“中止”标志,请等待每个线程自己注意到并自行终止-就我而言,我可以遍历集合并依次向每个线程发出Thread.Abort。

在那种情况下,我找不到直接与Thread类一起工作的更好方法。正如Eric Lippert所提到的,其他只是高级抽象,当可用的高级实现无法满足您的需要时,使用低级类是合适的。就像您有时在.NET无法满足您的确切需求时需要进行Win32 API调用一样,尽管最近有“进步”,但总有一些情况是Thread类是最佳选择。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.