MorkaLork Development

Interesting stuff I've picked up over the years...

C# Threading

2010-02-04 15:41:06 | 1830 views | C# csharp thread threads

Index






What are threads?


If you're not certain what threads are, read this article on MSDN, it' very good.

Back to top



Why use threads?


If you have an application that would be noticeably faster by running one or more processes at the same time it might be a good idea to use threads. Threads, however, should not be used because you know how to use them as they make debugging a lot harder and performance a bit unpredictable.

This article will just show examples of how to use threads, not when.

Back to top



A simple example


Here follows a simple example where we create a thread that runs a simple task for us:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static void Main(string[] args) {
//Create a new thread
Thread thread = new Thread(Work);

//Run thread
thread.Start();

//Wait until thread is done
thread.Join();

//...
Console.WriteLine("We have run our first thread!");
Console.Read();
}

static void Work() {
Console.WriteLine("Hello World!");
}
}
}


Output:
Hello World!
We have run our first thread!

What we do here is that we create a new thread that runs the method Work(). An application always has a 'main thread' on which the application runs. What we do here is that parallel to that main thread we open up a new thread that call the Work() method. Using the Join() method (in the Main() method) we tell the calling thread (main thread in this case) to hold on until the thread has finished, returned, and joined with it.


Let's look at the difference between joining and not joining threads. In this following example we'll create a number of threads all calling the same methods in a loop. After the loop is done it will output "Done!" to the console window:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[3];

static void Main(string[] args) {
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(Work);
threads[i].Name = "thread_" + i;
threads[i].Start();
}

Console.WriteLine("Done!");
Console.Read();
}

static void Work() {
for (int i = 0; i < 3; i++) {
Console.WriteLine("{0} run #{1}", Thread.CurrentThread.Name, i);
}
}
}
}


Output:
thread_0 run #0
thread_0 run #1
thread_0 run #2
thread_1 run #0
thread_1 run #1
thread_1 run #2
Done!
thread_2 run #0
thread_2 run #1
thread_2 run #2

This is not what we expect or want, but it is because the after the last thread is created in the loop, the main thread continues the execution in Main and outputs "Done!" before thread_2 has manage to start working in Work().

To solve this, we have to join the threads after the loop, before the main thread can continue execution:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[3];

static void Main(string[] args) {
for (int i = 0; i < threads.Length; i++) {
threads[i] = new Thread(Work);
threads[i].Name = "thread_" + i;
threads[i].Start();
}

for (int i = 0; i < threads.Length; i++) {
threads[i].Join();
}

Console.WriteLine("Done!");
Console.Read();
}

static void Work() {
for (int i = 0; i < 3; i++) {
Console.WriteLine("{0} run #{1}", Thread.CurrentThread.Name, i);
}
}
}
}


Output:
thread_0 run #0
thread_0 run #1
thread_1 run #0
thread_1 run #1
thread_1 run #2
thread_2 run #0
thread_2 run #1
thread_2 run #2
thread_0 run #2
Done!

Now, as we can see, the results are a bit random. Sometimes we get the result in the right order and sometimes we don't This is not optimal. What we need to do is make sure that every thread executes in the proper order.
We will not continue on this specific example, but the theory that we'll go into further on can be used to improve above example.

Back to top



Lock


Sometimes multiple threads access the same method or the same object in memory. This can cause unexpected results to occur and can be dealt with using the lock statement. Whenever you have a critical section (such as our output section where we want every thread to be outputted in an orderly fashion) you can put it inside a lock statement making sure that only one thread can access it at any given time.
The lock statements is really just a wrapper for the Monitor class, but we won't go deeper into the monitor class at this stage (we'll have an entire chapter on the Monitor class further down).

In the example we're gonna build we want to output falling letters in random columns. We want each column of falling letters to be run a separate thread, which is logical, so we design our application like this:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[5];
static Random random = new Random();
static int MAX_X = Console.WindowWidth;
static int MAX_Y = Console.WindowHeight;

static void Main(string[] args) {
Console.ForegroundColor = ConsoleColor.DarkGreen;

for (int i = 0; i < 5; i++) {
threads[i] = new Thread(Work);
threads[i].Start();
}

Console.Read();
}

static void Work() {
//Get a starting position
int x = random.Next(1, MAX_X);
int y = random.Next(1, MAX_Y);

for (int i = 0; i < 5; i++) {
//Move the cursor
Console.SetCursorPosition(x, y);

//Output character
Console.Write('x');

//Increment y-axis
y++;

//Sleep, for a smooth effect
Thread.Sleep(300);
}
}
}
}


This will start 5 threads, they in turn will all start running the Work() method which outputs the falling columns. However, the result may look like this:

This is how the failed column app might look!

Now that's not at all what we wanted! We wanted nice ordered columns, not broken up green freaks.
The problem here is that the Console class is static which means that all threads (no matter how many) can only access the Console class one at a time. This means that while one of the threads might be changing the cursor position, the other one might be using the Write method to output to the console. This causes the strange behavior in our application, so we must rethink our approach.

First, we need to locate the "critical section". This is the section where we must make sure that the same thread that does A also does the upcoming B, meaning that if thread 2 changes the cursor position then thread 2 must be the next thread using the Write method.

The critical section

We'll use the lock statement here to make sure only one thread at a time can access this critical section:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[5];
static Random random = new Random();
static int MAX_X = Console.WindowWidth;
static int MAX_Y = Console.WindowHeight;
static object syncLock = new object();

static void Main(string[] args) {
Console.ForegroundColor = ConsoleColor.DarkGreen;

for (int i = 0; i < 5; i++) {
threads[i] = new Thread(Work);
threads[i].Start();
}

Console.Read();
}

static void Work() {
//Get a starting position
int x = random.Next(1, MAX_X);
int y = random.Next(1, MAX_Y);

for (int i = 0; i < 5; i++) {
lock (syncLock) {
//Move the cursor
Console.SetCursorPosition(x, y);

//Output character
Console.Write('x');
}

//Increment y-axis
y++;

//Sleep, for a smooth effect
Thread.Sleep(300);
}
}
}
}


If we run the application now, we'll see a much more accurate result:

The correct result!

Back to top



Mutex


Instead of using lock we could use a Mutex object. A Mutex works like the lock but can be named to be visible throughout the entire operative system.
However, the Mutex class is noticeably slower (almost 100 times) than the Monitor class, so choose carefully.

This is the previous example using Mutex instead of Monitor:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[5];
static Random random = new Random();
static int MAX_X = Console.WindowWidth;
static int MAX_Y = Console.WindowHeight;
static Mutex mutex_lock = new Mutex();

static void Main(string[] args) {
Console.ForegroundColor = ConsoleColor.DarkGreen;

for (int i = 0; i < 5; i++) {
threads[i] = new Thread(Work);
threads[i].Start();
}

Console.Read();
}

static void Work() {
//Get a starting position
int x = random.Next(1, MAX_X);
int y = random.Next(1, MAX_Y);

for (int i = 0; i < 5; i++) {
//Lock the thread
mutex_lock.WaitOne();

//Move the cursor
Console.SetCursorPosition(x, y);

//Output character
Console.Write('x');

//Release lock so to let the next thread work
mutex_lock.ReleaseMutex();

//Increment y-axis
y++;

//Sleep, for a smooth effect
Thread.Sleep(300);
}
}
}
}


Back to top



Semaphores


The semaphore class works similar to the Monitor and Mutex class but lets you set a limit on how many threads has access to a critical area. It's often described as a nightclub (the semaphore) where the visitors (threads) stands in a queue outside the nightclub waiting for someone to leave in order to gain entrance.

Here is an example on how to use the Semaphore class:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static Thread[] threads = new Thread[10];
static Semaphore sem = new Semaphore(3, 3);

static void Main(string[] args) {
Console.ForegroundColor = ConsoleColor.DarkGreen;

for (int i = 0; i < 10; i++) {
threads[i] = new Thread(NightClub);
threads[i].Name = "thread_" + i;
threads[i].Start();
}

Console.Read();
}

static void NightClub() {
Console.WriteLine("{0} is waiting in line...", Thread.CurrentThread.Name);
sem.WaitOne();
Console.WriteLine("{0} enters the night club!", Thread.CurrentThread.Name);
Thread.Sleep(300);
Console.WriteLine("{0} is leaving the night club", Thread.CurrentThread.Name);
sem.Release();
}
}
}


Output:
thread_0 is waiting in line...
thread_0 enters the night club!
thread_1 is waiting in line...
thread_1 enters the night club!
thread_2 is waiting in line...
thread_2 enters the night club!
thread_3 is waiting in line...
thread_4 is waiting in line...
thread_5 is waiting in line...
thread_6 is waiting in line...
thread_7 is waiting in line...
thread_8 is waiting in line...
thread_9 is waiting in line...
thread_1 is leaving the night club
thread_0 is leaving the night club
thread_5 enters the night club!
thread_8 enters the night club!
thread_2 is leaving the night club
thread_6 enters the night club!
thread_8 is leaving the night club
thread_3 enters the night club!
thread_5 is leaving the night club
thread_4 enters the night club!
thread_6 is leaving the night club
thread_7 enters the night club!
thread_3 is leaving the night club
thread_4 is leaving the night club
thread_7 is leaving the night club
thread_9 enters the night club!
thread_9 is leaving the night club

Back to top



Monitor


The monitor class lets you lock objects that you might consider to be critical sections. The lock statement is a wrapper for the monitor class, but the monitor class can do more than just lock the object.

The Wait() and Pulse()/PulseAll() methods lets you block and unblock certain threads. Using this you can decide when to release a thread and let it do its work.

Here's an example:



using System;
using System.Threading;

namespace MorkaThreading {
class Program {
static object syncLock = new object();
static bool check = false;

static void Main(string[] args) {
new Thread(Work).Start();

Console.ReadLine();

lock (syncLock) {
check = true;
Monitor.Pulse(syncLock);
}

Console.Read();
}

static void Work() {
lock (syncLock) {
while (!check) {
Monitor.Wait(syncLock);
}
}

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


This application will wait for the user to press enter. When the enter button is pressed the Monitor class sends a pulse to the waiting thread. In this example we use Pulse() since we only have one thread waiting. If there were more than one thread that we would want to release we'd have to use PulseAll().

Back to top


Article comments

Feel free to comment this article using a facebook profile.

I'm using facebook accounts for identification since even akismet couldn't handle all the spam I receive every day.