In Threading in C# by Joseph Albahai has a nice section entitled
Threading’s Uses and Misuses. It lists five common use cases for threading.
When To
Maintaining a responsive user interface
By running time-consuming
tasks on a parallel “worker” thread,
the main UI thread is free to continue
processing keyboard and mouse events.
Making efficient use of an otherwise blocked CPU
Multithreading is useful when a thread
is awaiting a response from another
computer or piece of hardware. While
one thread is blocked while performing
the task, other threads can take
advantage of the otherwise unburdened
computer.
Parallel programming
Code that performs intensive
calculations can execute faster on
multicore or multiprocessor computers
if the workload is shared among
multiple threads in a
“divide-and-conquer” strategy
Speculative execution
On multicore machines, you can
sometimes improve performance by
predicting something that might need
to be done, and then doing it ahead
of time. LINQPad uses this technique
to speed up the creation of new
queries. A variation is to run a
number of different algorithms in
parallel that all solve the same task.
Whichever one finishes first
“wins”—this is effective when you
can’t know ahead of time which
algorithm will execute fastest.
Allowing requests to be processed simultaneously
On a server, client requests can
arrive concurrently and so need to be
handled in parallel (the .NET
Framework creates threads for this
automatically if you use ASP.NET, WCF,
Web Services, or Remoting). This can
also be useful on a client (e.g.,
handling peer-to-peer networking—or
even multiple requests from the user).
When not to
He goes on to caution the reader
With technologies such as ASP.NET and
WCF, you may be unaware that
multithreading is even taking
place—unless you access shared data
(perhaps via static fields) without
appropriate locking, running afoul of
thread safety.
Threads also come with
strings attached. The biggest is that
multithreading can increase
complexity. Having lots of threads
does not in and of itself create much
complexity; it’s the interaction
between threads (typically via shared
data) that does. This applies whether
or not the interaction is intentional,
and can cause long development cycles
and an ongoing susceptibility to
intermittent and nonreproducible bugs.
For this reason, it pays to keep
interaction to a minimum, and to
stick to simple and proven designs
wherever possible. This article
focuses largely on dealing with just
these complexities; remove the
interaction and there’s much less to
say!
A good strategy is to
encapsulate multithreading logic into
reusable classes that can be
independently examined and tested.
The Framework itself offers many
higher-level threading constructs,
which we cover later.
Threading also
incurs a resource and CPU cost in
scheduling and switching threads (when
there are more active threads than
CPU cores)—and there’s also a
creation/tear-down cost.
Multithreading will not always speed
up your application—it can even slow
it down if used excessively or
inappropriately. For example, when
heavy disk I/O is involved, it can be
faster to have a couple of worker
threads run tasks in sequence than to
have 10 threads executing at once.
(In Signaling with Wait and Pulse, we
describe how to implement a
producer/consumer queue, which
provides just this functionality.)