Monday, 10 March 2014

Threading Overview and Concepts in C#

How Threading Works
Multithreading is managed internally by a thread scheduler, a
function the CLR typically delegates to the operating system. A
thread scheduler ensures all active threads are allocated
appropriate execution time, and that threads that are waiting or
blocked – for instance – on an exclusive lock, or on user input –
do not consume CPU time.
On a single-processor computer, a thread scheduler performs
time-slicing – rapidly switching execution between each of the
active threads. This results in "choppy" behavior, such as in the
very first example, where each block of a repeating X or Y
character corresponds to a time-slice allocated to the thread.
Under Windows XP, a time-slice is typically in the tens-ofmilliseconds
region – chosen such as to be much larger than the
CPU overhead in actually switching context between one thread
and another (which is typically in the few-microseconds region).
On a multi-processor computer, multithreading is implemented
with a mixture of time-slicing and genuine concurrency – where
different threads run code simultaneously on different CPUs. It's
almost certain there will still be some time-slicing, because of the
operating system's need to service its own threads – as well as
those of other applications.
A thread is said to be preempted when its execution is interrupted
due to an external factor such as time-slicing. In most situations,
a thread has no control over when and where it's preempted.
Threads vs. Processes
All threads within a single application are logically contained within a process – the operating
system unit in which an application runs.
Threads have certain similarities to processes – for instance, processes are typically time-sliced
with other processes running on the computer in much the same way as threads within a single
C# application. The key difference is that processes are fully isolated from each other; threads
share (heap) memory with other threads running in the same application. This is what makes
threads useful: one thread can be fetching data in the background, while another thread is
displaying the data as it arrives.
7
When to Use Threads
A common application for multithreading is performing time-consuming tasks in the background.
The main thread keeps running, while the worker thread does its background job. With Windows
Forms or WPF applications, if the main thread is tied up performing a lengthy operation,
keyboard and mouse messages cannot be processed, and the application becomes unresponsive.
For this reason, it’s worth running time-consuming tasks on worker threads even if the main
thread has the user stuck on a “Processing… please wait” modal dialog in cases where the
program can’t proceed until a particular task is complete. This ensures the application doesn’t get
tagged as “Not Responding” by the operating system, enticing the user to forcibly end the process
in frustration! The modal dialog approach also allows for implementing a "Cancel" button, since
the modal form will continue to receive events while the actual task is performed on the worker
thread. The BackgroundWorker class assists in just this pattern of use.
In the case of non-UI applications, such as a Windows Service, multithreading makes particular
sense when a task is potentially time-consuming because it’s awaiting a response from another
computer (such as an application server, database server, or client). Having a worker thread
perform the task means the instigating thread is immediately free to do other things.
Another use for multithreading is in methods that perform intensive calculations. Such methods
can execute faster on a multi-processor computer if the workload is divided amongst multiple
threads. (One can test for the number of processors via the Environment.ProcessorCount
property).
A C# application can become multi-threaded in two ways: either by explicitly creating and
running additional threads, or using a feature of the .NET framework that implicitly creates
threads – such as BackgroundWorker, thread pooling, a threading timer, a Remoting server, or a
Web Services or ASP.NET application. In these latter cases, one has no choice but to embrace
multithreading. A single-threaded ASP.NET web server would not be cool – even if such a thing
were possible! Fortunately, with stateless application servers, multithreading is usually fairly
simple; one's only concern perhaps being in providing appropriate locking mechanisms around
data cached in static variables.
When Not to Use Threads
Multithreading also comes with disadvantages. The biggest is that it can lead to vastly more
complex programs. Having multiple threads does not in itself create complexity; it's the
interaction between the threads that creates complexity. This applies whether or not the
interaction is intentional, and can result long development cycles, as well as an ongoing
susceptibility to intermittent and non-reproducable bugs. For this reason, it pays to keep such
interaction in a multi-threaded design simple – or not use multithreading at all – unless you have
a peculiar penchant for re-writing and debugging!
Multithreading also comes with a resource and CPU cost in allocating and switching threads if
used excessively. In particular, when heavy disk I/O is involved, it can be faster to have just one
or two workers thread performing tasks in sequence, rather than having a multitude of threads
each executing a task at the same time. Later we describe how to implement a
Producer/Consumer queue, which provides just this functionality.
8
Creating and Starting Threads
Threads are created using the Thread class’s constructor, passing in a ThreadStart delegate –
indicating the method where execution should begin. Here’s how the ThreadStart delegate is
defined:
public delegate void ThreadStart();
Calling Start on the thread then sets it running. The thread continues until its method returns, at
which point the thread ends. Here’s an example, using the expanded C# syntax for creating a
TheadStart delegate:
class ThreadTest {
static void Main() {
Thread t = new Thread (new ThreadStart (Go));
t.Start(); // Run Go() on the new thread.
Go(); // Simultaneously run Go() in the main thread.
}
static void Go() { Console.WriteLine ("hello!"); }
In this example, thread t executes Go() – at (much) the same time the main thread calls Go(). The
result is two near-instant hellos:
hello!
hello!
A thread can be created more conveniently using C#'s shortcut syntax for instantiating delegates:
static void Main() {
Thread t = new Thread (Go); // No need to explicitly use ThreadStart
t.Start();
...
}
static void Go() { ... }
In this case, a ThreadStart delegate is inferred automatically by the compiler. Another shortcut
is to use an anonymous method to start the thread:
static void Main() {
Thread t = new Thread (delegate() { Console.WriteLine ("Hello!"); });
t.Start();
}
A thread has an IsAlive property that returns true after its Start() method has been called, up
until the thread ends.
A thread, once ended, cannot be re-started.

No comments:

Post a Comment

Note: only a member of this blog may post a comment.