有没有可能最好使用一个普通的旧 Thread 对象而不是一个新的结构?

我看到很多人在博客文章和这里,要么避免或建议反对使用 Thread类在最近版本的 C # (我的意思是当然4.0 + ,增加了 Task和朋友)。甚至在此之前,对于一个普通的旧线程的功能在许多情况下可以被 ThreadPool类替换这一事实也存在争议。

此外,其他专门的机制进一步使得 Thread类不那么吸引人,例如 Timer取代了丑陋的 Thread + Sleep组合,而对于 GUI,我们有 BackgroundWorker等等。

尽管如此,对于一些人(包括我自己)来说,Thread似乎仍然是一个非常熟悉的概念,这些人在面对一个涉及某种并行执行的任务时,会直接使用老式的 Thread类。我最近一直在想是不是该改过自新了。

因此,我的问题是,有没有必要或有用的情况下,使用一个普通的旧 Thread对象,而不是上述结构之一?

4120 次浏览

Threads are a basic building block for certain things (namely parallelism and asynchrony) and thus should not be taken away. However, for most people and most use cases there are more appropriate things to use which you mentioned, such as thread pools (which provide a nice way of handling many small jobs in parallel without overloading the machine by spawning 2000 threads at once), BackgroundWorker (which encapsulates useful events for a single shortlived piece of work).

But just because in many cases those are more appropriate as they shield the programmer from needlessly reinventing the wheel, doing stupid mistakes and the like, that does not mean that the Thread class is obsolete. It is still used by the abstractions named above and you would still need it if you need fine-grained control over threads that is not covered by the more special classes.

In a similar vein, .NET doesn't forbid the use of arrays, despite List<T> being a better fit for many cases where people use arrays. Simply because you may still want to build things that are not covered by the standard lib.

You could compare the Thread class to ADO.NET. It's not the recommended tool for getting the job done, but its not obsolete. Other tools build on top of it to ease the job.

Its not wrong to use the Thread class over other things, especially if those things don't provide a functionality that you need.

Task and Thread are different abstractions. If you want to model a thread, the Thread class is still the most appropriate choice. E.g. if you need to interact with the current thread, I don't see any better types for this.

However, as you point out .NET has added several dedicated abstractions which are preferable over Thread in many cases.

The new options make direct use and management of the (expensive) threads less frequent.

people that, when confronted with a task that involves some kind of parallel execution, jump directly to using the good old Thread class.

Which is a very expensive and relatively complex way of doing stuff in parallel.

Note that the expense matters most: You cannot use a full thread to do a small job, it would be counterproductive. The ThreadPool combats the costs, the Task class the complexities (exceptions, waiting and canceling).

The Thread class cannot be made obsolete because obviously it is an implementation detail of all those other patterns you mention.

But that's not really your question; your question is

are there any cases when it's necessary or useful to use a plain old Thread object instead of one of the above constructs?

Sure. In precisely those cases where one of the higher-level constructs does not meet your needs.

My advice is that if you find yourself in a situation where existing higher-abstraction tools do not meet your needs, and you wish to implement a solution using threads, then you should identify the missing abstraction that you really need, and then implement that abstraction using threads, and then use the abstraction.

It's not definitely obsolete.

The problem with multithreaded apps is that they are very hard to get right (often indeterministic behavior, input, output and also internal state is important), so a programmer should push as much work as possible to framework/tools. Abstract it away. But, the mortal enemy of abstraction is performance.

So my question is, are there any cases when it's necessary or useful to use a plain old Thread object instead of one of the above constructs?

I'd go with Threads and locks only if there will be serious performance problems, high performance goals.

The Thread class is not obsolete, it is still useful in special circumstances.

Where I work we wrote a 'background processor' as part of a content management system: a Windows service that monitors directories, e-mail addresses and RSS feeds, and every time something new shows up execute a task on it - typically to import the data.

Attempts to use the thread pool for this did not work: it tries to execute too much stuff at the same time and trash the disks, so we implemented our own polling and execution system using directly the Thread class.

To answer the question of "are there any cases when it's necessary or useful to use a plain old Thread object", I'd say a plain old Thread is useful (but not necessary) when you have a long running process that you won't ever interact with from a different thread.

For example, if you're writing an application that subscribes to receive messages from some sort of message queue and you're application is going to do more than just process those messages then it would be useful to use a Thread because the thread will be self-contained (i.e. you aren't waiting on it to get done), and it isn't short-lived. Using the ThreadPool class is more for queuing up a bunch of short-lived work items and allowing the ThreadPool class manage efficiently processing each one as a new Thread is available. Tasks can be used where you would use Thread directly, but in the above scenario I don't think they would buy you much. They help you interact with the thread more easily (which the above scenario doesn't need) and they help determine how many Threads actually should be used for the given set of tasks based on the number of processors you have (which isn't what you want, so you'd tell the Task your thing is LongRunning in which case in the current 4.0 implementation it would simply create a separate non-pooled Thread).

I've always used the Thread class when I need to keep count and control over the threads I've spun up. I realize I could use the threadpool to hold all of my outstanding work, but I've never found a good way to keep track of how much work is currently being done or what the status is.

Instead, I create a collection and place the threads in them after I spin them up - the very last thing a thread does is remove itself from the collection. That way, I can always tell how many threads are running, and I can use the collection to ask each what it's doing. If there's a case when I need to kill them all, normally you'd have to set some kind of "Abort" flag in your application, wait for every thread to notice that on its own and self-terminate - in my case, I can walk the collection and issue a Thread.Abort to each one in turn.

In that case, I haven't found a better way that working directly with the Thread class. As Eric Lippert mentioned, the others are just higher-level abstractions, and it's appropriate to work with the lower-level classes when the available high-level implementations don't meet your need. Just as you sometimes need to do Win32 API calls when .NET doesn't address your exact needs, there will always be cases where the Thread class is the best choice despite recent "advancements."

Probably not the answer you were expecting, but I use Thread all the time when coding against the .NET Micro Framework. MF is quite cut down and doesn't include higher level abstractions and the Thread class is super flexible when you need to get the last bit of performance out of a low MHz CPU.