241-301 3SA04: .NET – Threading Application in C#

donwload lab here!!!

C# on .NET framework supports parallel execution of code through multithreading. A thread is an independent execution path, able to run simultaneously with other threads.

A C# program starts in a single thread created automatically by the CLR and operating system (the “main” thread), and is made multi-threaded by creating additional threads.

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-slicingrapidly switching execution between each of the active threads. Under Windows XP, a time-slice is typically in the tens-of-milliseconds 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.

1. 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:

Example1 :  ThreadStart

using System;
using System.Threading;
namespace Example
{
     class Program1
     {

          static void Main(string[] args)
          {
               Thread worker = new Thread (new ThreadStart (Go));
               worker.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 worker executes Go() – at (much) the same time the main thread calls Go(). The result is two near-instant hellos:

hello!
hello!

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.

Naming Threads

A thread can be named via its Name property. This is of great benefit in debugging: as well as being able to Console.WriteLine a thread’s name, Microsoft Visual Studio picks up a thread’s name and displays it in the Debug Location toolbar. A thread’s name can be set at any time – but only once – attempts to subsequently change it will throw an exception.

The application’s main thread can also be assigned a name – in the following example the main thread is accessed via the CurrentThread static property:

Example2 : CurrentThread.Name

class Program2
{
     static void Main(string[] args)
     {
          Thread.CurrentThread.Name = "main";
          Thread worker = new Thread(new ThreadStart(Go));

          worker.Name = "worker";

          worker.Start();   // Run Go() on the new thread.

          Go();        // Simultaneously run Go() in the main thread.
     }

     static void Go()
     {
          Console.WriteLine("hello from " + Thread.CurrentThread.Name);
     }
}
Hello from main
Hello from worker

Foreground and Background Threads

By default, threads are foreground threads, meaning they keep the application alive for as long as any one of them is running. C# also supports background threads, which don’t keep the application alive on their own – terminating immediately once all foreground threads, have ended. A thread’s IsBackground property controls its background status, as in the following example:

Example3 : IsBackground

class Example3
{

     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));

          if (args.Length > 0) worker.IsBackground = true;

          worker.Start();
     }

     static void Go()
     {
          Console.ReadLine();
     }
}

If the program is called with no arguments, the worker thread runs in its default foreground mode, and will wait on the ReadLine statement, waiting for the user to hit Enter. Meanwhile, the main thread exits, but the application keeps running because a foreground thread is still alive.

If on the other hand an argument is passed to Main(), the worker is assigned background status, and the program exits almost immediately as the main thread ends – terminating the ReadLine.

2. Basic Synchronization

One can query a thread’s execution status via its ThreadState property. Figure 1 shows one “layer” of the ThreadState enumeration. ThreadState is horribly designed, in that it combines three “layers” of state using bitwise flags, the members within each layer being themselves mutually exclusive. Here are all three layers:

  • the running / blocking / aborting status (as shown in Figure 1)
  • the background/foreground status (ThreadState.Background)
  • the progress towards suspension via the deprecated Suspend method (ThreadState.SuspendRequested and ThreadState.Suspended)

IsAlive  returns true if the thread’s blocked or suspended (the only time it returns false is before the thread has started, and after it has ended).

sync

Figure 1: Thread State Diagram

Synchronization Essentials

The following tables summarize the .NET tools available for coordinating or synchronizing the actions of threads:

Simple Blocking Methods

Construct Purpose
Sleep Blocks for a given time period.
Join Waits for another thread to finish.

Locking Constructs

Construct Purpose Cross-Process? Speed
lock Ensures just one thread can access a resource, or section of code. no fast
Mutex Ensures just one thread can access a resource, or section of code.
Can be used to prevent multiple instances of an application from starting.
yes moderate
Semaphore Ensures not more than a specified number of threads can access a resource, or section of code. yes moderate

(Synchronization Contexts are also provided, for automatic locking).

Signaling Constructs

Construct Purpose Cross-Process? Speed
EventWaitHandle Allows a thread to wait until it receives a signal from another thread. yes moderate
Wait and Pulse* Allows a thread to wait until a custom blocking condition is met. no moderate

Blocking

When a thread waits or pauses as a result of using the constructs listed in the tables above, it’s said to be blocked. Once blocked, a thread immediately relinquishes its allocation of CPU time, adds WaitSleepJoin to its ThreadState property, and doesn’t get re-scheduled until unblocked. Unblocking happens in one of four ways (the computer’s power button doesn’t count!):

  • by the blocking condition being satisfied
  • by the operation timing out (if a timeout is specified)
  • by being interrupted via Thread.Interrupt
  • by being aborted via Thread.Abort

A thread is not deemed blocked if its execution is paused via the (deprecated) Suspend method.

Sleeping

Calling Thread.Sleep blocks the current thread for the given time period (or until interrupted):

Example4 : Sleep

class Example4
{
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
     worker.Start();
     }

     static void Go()
     {
          Thread.Sleep(0);             // relinquish CPU time-slice
          Thread.Sleep(1000);          // sleep for 1000 milliseconds
          Thread.Sleep(TimeSpan.FromHours(1));   // sleep for 1 hour
          Thread.Sleep(Timeout.Infinite);        // sleep until interrupted
     }
}

More precisely, Thread.Sleep relinquishes the CPU, requesting that the thread is not re-scheduled until the given time period has elapsed. Thread.Sleep(0) relinquishes the CPU just long enough to allow any other active threads present in a time-slicing queue (should there be one) to be executed.

Joining a Thread

You can block until another thread ends by calling Join:

The Join method also accepts a timeout argument – in milliseconds, or as a TimeSpan, returning false if the Join timed out rather than found the end of the thread. Join with a timeout functions rather like Sleep – in fact the following two lines of code are almost identical:

Thread.Sleep (1000);
Thread.CurrentThread.Join (1000);

(Their difference is apparent only in single-threaded apartment applications with COM interoperability, and stems from the subtleties in Windows message pumping semantics described previously: Join keeps message pumping alive while blocked; Sleep suspends message pumping).

Example5 : Join

class Example5
{
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          worker.Start();
          worker.Join();    // Wait until thread worker finishes
          Console.WriteLine("Thread worker's ReadLine complete!");
     }

     static void Go()
     {
          Console.ReadLine();
     }
}

3. Locking and Thread Safety

Locking enforces exclusive access, and is used to ensure only one thread can enter particular sections of code at a time. For example, consider following class:

Example6 : Thread-not safe

class Example6
{
     static int val1, val2;
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          val1 = int.Parse(Console.ReadLine());
          val2 = int.Parse(Console.ReadLine());
          worker.Start();
          Go();
     }

     static void Go()
     {
          if (val2 != 0) Console.WriteLine(val1 / val2);
          val2 = 0;
     }
}

This is not thread-safe: if Go was called by two threads simultaneously it would be possible to get a division by zero error – because val2 could be set to zero in one thread right as the other thread was in between executing the if statement and Console.WriteLine.

Here’s how lock can fix the problem:

Example7 : lock

class Example7
{
     static int val1, val2;

     static object locker = new object();

     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          val1 = int.Parse(Console.ReadLine());
          val2 = int.Parse(Console.ReadLine());
          worker.Start();
          Go();
     }

     static void Go()
     {
          lock (locker)
          {
               if (val2 != 0) Console.WriteLine(val1 / val2);
               val2 = 0;
          }
     }
}

Only one thread can lock the synchronizing object (in this case locker) at a time, and any contending threads are blocked until the lock is released. If more than one thread contends the lock, they are queued – on a “ready queue” and granted the lock on a first-come, first-served basis as it becomes available. Exclusive locks are sometimes said to enforce serialized access to whatever’s protected by the lock, because one thread’s access cannot overlap with that of another. In this case, we’re protecting the logic inside the Go method, as well as the fields val1 and val2.

A thread blocked while awaiting a contended lock has a ThreadState of  WaitSleepJoin. Later we discuss how a thread blocked in this state can be forcibly released via another thread calling its Interrupt or Abort method. This is a fairly heavy-duty technique that might typically be used in ending a worker thread.

C#’s lock statement is in fact a syntactic shortcut for a call to the methods Monitor.Enter and Monitor.Exit, within a try-finally block. Here’s what’s actually happening within the Go method of the previous example:

Calling Monitor.Exit without first calling Monitor.Enter on the same object throws an exception.

Monitor also provides a TryEnter method allows a timeout to be specified – either in milliseconds or as a TimeSpan. The method then returns true – if a lock was obtained – or false – if no lock was obtained because the method timed out.  TryEnter can also be called with no argument, which “tests” the lock, timing out immediately if the lock can’t be obtained right away.

Example8 : Monitor

class Example8
{
     static int val1, val2;
     static object locker = new object();
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          val1 = int.Parse(Console.ReadLine());
          val2 = int.Parse(Console.ReadLine());
          worker.Start();
          Go();
     }

     static void Go()
     {
          Monitor.Enter(locker);
          try
          {
               if (val2 != 0) Console.WriteLine(val1 / val2);
               val2 = 0;
          }
          finally
          {
               Monitor.Exit(locker);
          }
     }
}

4. Interrupt and Abort

A blocked thread can be released prematurely in one of two ways:

  • via Thread.Interrupt
  • via Thread.Abort

This must happen via the activities of another thread; the waiting thread is powerless to do anything in its blocked state.

Interrupt

Calling Interrupt on a blocked thread forcibly releases it, throwing a ThreadInterruptedException, as follows:

Example9 : Interrupt

class Example9
{
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          worker.Start();
          worker.Interrupt();
     }

     static void Go()
     {
          try
          {
               Thread.Sleep(Timeout.Infinite);
          }
          catch (ThreadInterruptedException)
          {
               Console.Write("Forcibly ");
          }

          Console.WriteLine("Woken!");
     }
}

Forcibly Woken!

Interrupting a thread only releases it from its current (or next) wait: it does not cause the thread to end (unless, of course, the ThreadInterruptedException is unhandled!)

Interrupting a thread arbitrarily is dangerous, however, because any framework or third-party methods in the calling stack could unexpectedly receive the interrupt rather than your intended code. All it would take is for the thread to block briefly on a simple lock or synchronization resource, and any pending interruption would kick in. If the method wasn’t designed to be interrupted (with appropriate cleanup code in finally blocks) objects could be left in an unusable state, or resources incompletely released.

Interrupting a thread is safe when you know exactly where the thread is. Later we cover signaling constructs, which provide just such a means.

Abort

A blocked thread can also be forcibly released via its Abort method. This has an effect similar to calling Interrupt, except that a ThreadAbortException is thrown instead of a ThreadInterruptedException. Furthermore, the exception will be re-thrown at the end of the catch block (in an attempt to terminate the thread for good) unless Thread.ResetAbort is called within the catch block. In the interim, the thread has a ThreadState of AbortRequested.

The big difference, though, between Interrupt and Abort, is what happens when it’s called on a thread that is not blocked. While Interrupt waits until the thread next blocks before doing anything, Abort throws an exception on the thread right where it’s executing – maybe not even in your code. Aborting a non-blocked thread can have significant consequences.

5. Wait Handles

The lock statement (Monitor.Enter / Monitor.Exit) is one example of a thread synchronization construct. While lock is suitable for enforcing exclusive access to a particular resource or section of code, there are some synchronization tasks for which it’s clumsy or inadequate, such as signaling a waiting worker thread to begin a task.

The Win32 API has a richer set of synchronization constructs, and these are exposed in the .NET framework via the EventWaitHandle, Mutex and Semaphore classes. Some are more useful than others: the Mutex class, for instance, mostly doubles up on what’s provided by lock, while EventWaitHandle provides unique signaling functionality.

All three classes are based on the abstract WaitHandle class, although behaviorally, they are quite different. One of the things they do all have in common is that they can, optionally, be “named”, allowing them to work across all operating system processes, rather than across just the threads in the current process.

EventWaitHandle has two subclasses: AutoResetEvent and ManualResetEvent (neither being related to a C# event or delegate). Both classes derive all their functionality from their base class: their only difference being that they call the base class’s constructor with a different argument.

In terms of performance, the overhead with all Wait Handles typically runs in the few-microseconds region. Rarely is this of consequence in the context in which they are used.

AutoResetEvent

An AutoResetEvent is much like a ticket turnstile: inserting a ticket lets exactly one person through. The “auto” in the class’s name refers to the fact that an open turnstile automatically closes or “resets” after someone is let through. A thread waits, or blocks, at the turnstile by calling WaitOne (wait at this “one” turnstile until it opens) and a ticket is inserted by calling the Set method. If a number of threads call WaitOne, a queue builds up behind the turnstile. A ticket can come from any thread – in other words, any (unblocked) thread with access to the AutoResetEvent object can call Set on it to release one blocked thread.

If Set is called when no thread is waiting, the handle stays open for as long as it takes until some thread to call WaitOne. This behavior helps avoid a race between a thread heading for the turnstile, and a thread inserting a ticket (“oops, inserted the ticket a microsecond too soon, bad luck, now you’ll have to wait indefinitely!”) However calling Set repeatedly on a turnstile at which no-one is waiting doesn’t allow a whole party through when they arrive: only the next single person is let through and the extra tickets are “wasted”.

WaitOne accepts an optional timeout parameter – the method then returns false if the wait ended because of a timeout rather than obtaining the signal. WaitOne can also be instructed to exit the current synchronization context for the duration of the wait (if an automatic locking regime is in use) in order to prevent excessive blocking.

A Reset method is also provided that closes the turnstile – should it be open, without any waiting or blocking.

An AutoResetEvent can be created in one of two ways. The first is via its constructor:

EventWaitHandle wh = new AutoResetEvent (false);

If the boolean argument is true, the handle’s Set method is called automatically, immediately after construction. The other method of instantiatation is via its base class, EventWaitHandle:

EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto);

EventWaitHandle’s constructor also allows a ManualResetEvent to be created (by specifying EventResetMode.Manual).

One should call Close on a Wait Handle to release operating system resources once it’s no longer required. However, if a Wait Handle is going to be used for the life of an application (as in most of the examples in this section), one can be lazy and omit this step as it will be taken care of automatically during application domain tear-down.

In the following example, a thread is started whose job is simply to wait until signaled by another thread.

Example10 : AutoResetEvent

class Example10
{
     static EventWaitHandle wh = new AutoResetEvent (false);
     static void Main(string[] args)
     {
          Thread worker = new Thread(new ThreadStart(Go));
          worker.Start();
          Thread.Sleep (1000);    // Wait for some time...
          wh.Set();               // OK - wake it up
     }

     static void Go()
     {
          Console.WriteLine ("Waiting...");
          wh.WaitOne();                        // Wait for notification
          Console.WriteLine ("Notified");
      }
}

Waiting… (pause) Notified.

ManualResetEvent

A ManualResetEvent is a variation on AutoResetEvent. It differs in that it doesn’t automatically reset after a thread is let through on a WaitOne call, and so functions like a gate: calling Set opens the gate, allowing any number of threads that WaitOne at the gate through; calling Reset closes the gate, causing, potentially, a queue of waiters to accumulate until its next opened.

One could simulate this functionality with a boolean “gateOpen” field (declared with the volatile keyword) in combination with “spin-sleeping” – repeatedly checking the flag, and then sleeping for a short period of time.

ManualResetEvents are sometimes used to signal that a particular operation is complete, or that a thread’s completed initialization and is ready to perform work.

Work to be done

In this part we focalize on an application for IP Camera application. We create a worker thread for connecting to an IP camera and receiving the sequence of images encoded in MJPEG format.

app

Load the example project and study how we applied the above methods for our application with respect to the following items:

1. Thread creation and initialization.

2. Thread manipulation: Sleep, Join, Lock, Event and etc.

3. Thread release.

4. Thread synchronization.

** Diagram with description NEEDED in your report ***

Related posts: