ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Multithreading with C#

by Raffi Krikorian
08/06/2001

So far we have talked about, compared, and contrasted C#'s and Java's syntax and input/output functionalities -- this time around we are going to talk about threading.

Threads are a powerful abstraction for allowing parallelized operations: graphical updates can happen while another thread is performing computations, two threads can handle two simultaneous network requests from a single process, and the list goes on. This article will focus on the syntactical differences between Java and C# threading capabilities as well as presenting a translation of a few common Java usage patterns into C#.

Conceptually, threads provide a way to execute code in parallel within the same program -- each thread executes instructions "simultaneously" (of course, on a single processor machine, this is accomplished by interweaving operations of the threads that are running) but within a shared memory space, so each thread can have access to same the data structures within a program. Because of this characteristic, the subtleties of multi-threaded operation come into play, as a program is likely to want to safely share data between the many different executing threads.

Creating and Running Threads

Comment on this articleWhat's your experience with threading in C#?
Post your comments

Java provides most of its threading functionality in the java.lang.Thread and java.lang.Runnable classes. Creating a thread is as simple as extending the Thread class and calling start(); a Thread may also be defined by authoring a class that implements Runnable and having that class passed into a Thread. Take the following simple program -- we will have two threads both counting from 1 to 5 simultaneously and printing it out

using System;
public class ThreadingExample
extends Object {
   public static void main( String args[] ) {
     Thread[] threads = new Thread[2];
     for( int count=1;count<threads.length;count++ ) {
       threads[count] = new Thread( new Runnable() {
         public void run() {
          count();  

         }

       } );
       threads[count].start();

     }

   }

   public static void count() {
     for( int count=1;count<=5;count++ )
       System.out.print( count + " " );

   }

}

We can translate this into C#, making use of the System.Threading.Thread class and the System.Threading.ThreadStart delegate:

using System.Threading;
public class ThreadingExample : Object {
   public static void Main() {
     Thread[] threads = new Thread[2];
     for( int count=1;count<threads.Length;count++ ) {
       threads[count] = new Thread( new ThreadStart( Count ) );
       threads[count].Start();

     }      
        
   }

   public static void Count() {
     for( int count=1;count<=5;count++ )
       Console.Write( count + " " );

   }

}

This example is a little deceiving, however. Where Java allows the java.lang.Thread class to be extended and the java.lang.Runnable interface to be implemented, C# does not provide these facilities. A C# Thread is sealed and therefore it must be constructed with a ThreadStart delegate that refers to a method that has both its parameters and return value as void -- this simply means that instead of using the inner class pattern (as above), an object will need to be created and one of the object's methods must be passed to the thread for execution.

Using Threads

There are a bunch of standard operations that a Java programmer wants to be able to perform on Threads: test if the thread is alive, join a thread until it dies, kill a thread, etc.

Table 1: Thread management functions
A mapping between Java's java.lang.Thread methods and their equivalent in C#'s System.Threading.Thread object

Java C# Notes
setDaemon( boolean on) method IsBackground set property

Makes a given thread a daemon thread (the program will terminate when all that is left are daemon threads).

isDaemon() method IsBackground get property

Returns true if the thread is a background thread.

isAlive() method IsAlive get property

Returns true if the thread is alive.

interrupt() method Interrupt() method

While in Java this sets the "interrupted status" of the thread and can be checked to see whether a thread has been interrupted, there is no such equivalent check in C#. However, in C#, calling Interrupt on a thread that is not blocking will cause the next blocking call to automatically unblock.

isInterrupted() method n/a

Returns true if this thread has been interrupted.

sleep( long millis ) and sleep( long millis, int nanos ) Sleep( int millisecondTimeout ) and Sleep( System.TimeSpan ) methods

A static call that pauses the thread of execution for a given amount of time or until the thread is interrupted -- this method will throw a java.lang.InterruptedException in Java and a System.Threading. ThreadInterruptedException in C# (remember that all C# exceptions are run-time).

join(), and join( long millis ), and join( long millis, int nanos ) methods Join(), Join( int millisecondTimeout ), and Join( System.TimeSpan ) methods

Unlike the Java join methods which are simply timeouts, the C# versions return a boolean upon termination to signify whether the thread died (true) or the timeout expired (false).

suspend() method Suspend() method

Functionally the same -- the C# methods exhibit the same properties that caused the suspend() method in java.lang.Thread to be deprecated. This method is deadlock-prone: if a thread holding a lock on a system-critical resource is suspended, then no other threads can access the resource until the former thread is resumed.

resume() method Resume() method

Resumes a suspended thread.

stop() method Abort() method

See "Stopping Threads" following this table.

Stopping Threads

The Thread.stop method in Java has been deprecated, as it can silently put the running program into an inconsistent state; upon a stop() method call, an unchecked java.lang.ThreadDeath error will propagate up the running thread's stack, unlocking any locked monitors as it goes along. As these locks are being indiscriminately released, the data they they are protecting may be left in an inconsistent state.

According to the current Java documentation, the recommended manner to stop a thread is to have the running thread check a variable which another thread could flip to signal a "time to die" condition such as in the following example

 // the condition variable
 private boolean timeToDie = false;   

 // a runnable that checks the condition variable
 // on every iteration
 class StoppableRunnable
 extends Runnable {
   public void run() {
     while( !timeToDie ) {
       // do stuff

     }

   }

 }

The above statements hold true for C#'s Abort method also -- upon calling Abort, an uncatchable System.Threading.ThreadAbortException will propagate up the stack, silently killing the thread. It too will free any monitor locks that the thread currently holds and may place the protected data structures into a bad state. I recommend using a pattern similar to the one laid out above to notify a thread that it is time to die.

In both languages, it is worth noting, calling either stop() or Abort() will cause the finally clauses of any try-catch-finally block it is currently in to execute.

Inter-Thread Synchronization

Conceptually, threads are pretty simple to understand. Practically however, because they can inter-operate on the same data structures, they are a cause for programmatic headaches. Take the ThreadingExample class described at the beginning of this article -- there are many different permutations of results that may appear on the console when the program is run. Anything from

 1 2 3 4 5 1 2 3 4 5

to

 1 1 2 2 3 3 4 4 5 5

or

 1 2 1 2 3 3 4 5 4 5

is possible. (In reality, both programs had to be modified to count from 1 to 50 for me to demonstrate this to myself on my laptop.) The output is left to nuances of the operating system's thread scheduler. Sometimes it is necessary to make sure that only one thread can access a given set of data structures so as to make sure the data structures stay consistent -- and that is why we need inter-thread synchronization semantics.

In order to guarantee a certain behavior we have to coordinate the actions of the two threads through the use of locks. Both languages allow you to acquire a lock on any reference object -- once that lock is acquired, the program block can be assured that it is the only block that has acquired it. Also by using that lock, a program can wait until a signal comes through the variable causing the thread to wake up.


Table 2: Thread synchronization
The keywords to know when wanting to synchronize actions between threads in the two languages

Java C# Notes
synchronized lock

The C# lock statement is actually syntactic sugar for using the System.Threading.Monitor class's Enter and Exit methods

Object.wait() Monitor.Wait( object obj )

There is no wait method inherent to an Object. To wait for a signal, the System.Threading.Monitor class needs to be used instead. Both of these methods need to be executed in synchronized blocks

Object.notify() Monitor.Pulse( object obj )

See the notes for Monitor.Wait above

Object.notify() Monitor.PulseAll( object obj )

See the notes for Monitor.Wait above

We can modify the given example to let the variables increase in lock step in Java by first adding a variable to synchronize on and then modifying the count() method to

   public static Object synchronizeVariable = "locking variable";

   public static void count() {
     synchronized( synchronizeVariable ) {
       for( int count=1;count<=5;count++ ) {
         System.out.print( count + " " );  
         synchronizeVariable.notifyAll();
         if( count < 5 )
           try {
             synchronizeVariable.wait();
            
           } catch( InterruptedException error ) {

           }

       }

     }

   }

What happens with these changes is that one thread, and one thread only (because only one thread can grab synchronizeVariable at at time), enters the for loop printing out 1. Next, it notifies all threads that are waiting on synchronizeVariable (although currently no threads are waiting) to wake up and attempt to grab the locking variable, and then it waits on the lock variable, which releases its hold on synchronizeVariable. The next thread can then enter the loop, print out 1, call notifyAll() to wake up the former thread and cause it to start attempting to grab hold of synchronizeVariable, then put itself into a wait(), releasing the variable to allow the former thread to grab it. This goes on until they have both printed 1 through 5 in lock step.

This can be easily translated into C# with a few syntactical changes

  public static Object synchronizeVariable = "locking variable";

  public static void Count() {
   lock( synchronizeVariable ) {
     for( int count=1;count<=5;count++ ) {
      Console.WriteLine( count + " " );nbsp; 
      Monitor.PulseAll( synchronizeVariable );
      if( count < 5 )
       Monitor.Wait( synchronizeVariable );

     }

   }

  }

Some C# Goodies

As we have come to expect from C#, the language has a few more built-in methods, classes, and functionalities that Java does not have. These may prove to be helpful to the hardcore Java threads programmer, as they are usually items that Java programmers either wish for, write, or import into their own code.

Enter/TryEnter/Exit


P2P and Web Services Speaker

Related Tutorials:

Building Web Services with .NET -- One of the key motivations behind .NET is the move towards web services. This tutorial provides an overview of the rich support in the Microsoft .NET Framework for building web services and networked applications.

Microsoft .NET and the Common Language Runtime -- This tutorial provides an overview of the .NET Framework, then delves into the Common Language Runtime and what it offers to developers building & deploying modern applications.


To obtain a lock on a variable in Java, you just need to surround the code with the synchronized keyword specifying the object that the program should attempt to acquire a lock on. Once a thread has started to execute code in the synchronized block, it is guaranteed to have already received a monitor lock on the object being synchronized on -- likewise, once a thread has left the block, it will have released the lock on the object. We have seen that C# has a similar construct called lock that operates on reference types. In addition to the lock construct, C# has provided access to its internal methods to acquire and release locks: Monitor.Enter( object obj )and Monitor.Exit( object obj ). Using these methods can buy a programmer the same benefits as using the lock construct, but it can also provide more elaborate locking abilities, such as being able to lock variables in one method and have them released at different times or different points in the code, depending on the code path.

A call to System.Threading.Monitor.Enter on the object to synchronize on will cause C# to either obtain the lock on the object, or block if another thread is currently holding lock (it will not block if a Thread already holding an object's lock attempts to acquire it again -- however, a programmer should ensure that a lock is released the same amount of times it is acquired). It should be noted that calling Interrupt() on a Thread which is blocking on an Enter to a lock will cause the method to unblock and throw a System.Threading.ThreadIntrerruptedException. Releasing a lock is easily done with the Monitor.Exit method -- this method will throw a System.Threading.SynchronizationLockException if it does not already own the lock on the object it is attempting to release.

What Java is lacking (and what C# has) is the ability to attempt to enter a synchronized code segment without blocking on the acquisition of the lock. C#'s Monitor class not only contains the Enter method, which blocks until it can be assured that it will be the only thread in the block, but it also contains a TryEnter method, which will either obtain a lock (optionally blocking for a given amount of time) or return with a return value of false, signifying that it cannot get a lock on the object.

Atomic Operations

As more syntactic sugar, the System.Threading.Interlocked class provides the ability for a program to synchronize access to variables that are shared amongst Threads. C# manages this by abstracting a few basic operations into "atomic" or non-splittable operations. To explain the problem that is being solved, take the following Java code block:

 public static int x = 1;

 public static void increment() {
   x = x + 1;

 }

If we have increment() called by two different threads simultaneously, x could be equal to either 2 or 3 at the end. This can happen with code from both threads interweaving with each other while looking up the variable of x and incrementing it before setting x again; both threads could read x as 1 before any thread has the chance to increment it and set x with the new value.

In both languages we can synchronize access to the x variable so that the threads can stay out of each other's way, but C# provides a cleaner solution through the use of the Interlocked class. Interlocked has a few methods such as Increment( ref int location ) and Decrement( ref int location ) that can take an integer, increment it, and return its new value all in one uninterruptable step, instead of explicitly creating a separate object that the code can synchronize on, such as this example in Java:

 public static Object locker = ...
 public static int x = 1;

 public static void increment() {
   synchronized( locker ) {
     x = x + 1;

   }

 }

C#'s Interlocked class can do the same operation with

 public static int x = 1;

 public static void Increment() {
   Interlocked.Increment( ref x );

 }

Interlocked also contains a method named Exchange, which allows for the setting of a variable from another variable in a single step.

ThreadPools

A lot of threaded applications create threads that spend a majority of their time in a waiting state waiting for a certain condition (key press, new I/O input, etc.). C# provides a single System.Threading.ThreadPool object to help with this problem -- using the ThreadPool and an event-based programming paradigm, the program can register a System.Threading.WaitHandle object (WaitHandles are C#'s object model for the wait and notify paradigm of coding) and a System.Threading.WaitOrTimerCallback delegate. Instead of having a full-fledged Thread waiting for the WaitHandle to release, the ThreadPool will monitor all the WaitHandles registered with it and then call the appropriate WaitOrTimerCallback delegate method when the WaitHandle has been released.

In this article we have seen the abstractions that C# provides for threaded and parallel operations. Most are similar to Java -- C# provides a Thread object that can run a provided method while also providing ways to synchronize access to certain blocks of code. As usual, C# also provides a few more syntactic sugars (or in some cases, exposes the underlying synchronization base) that Java programmers may find useful. By this point, we have covered most of what Java programmers need to get started in writing trivial to intermediate applications; we have focused on C# syntax, I/O, and now, threading. All that is really left will be covered in the next article: graphical applications.

Raffi Krikorian makes a career of hacking everything and anything. Professionally, he is the founding partner at Synthesis Studios: a technological design and consulting firm that orchestrates his disjointed train of thought.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.