5,426,531 members and growing! (15,048 online)
Email Password   helpLost your password?
General Programming » Threads, Processes & IPC » Multi-threading     Beginner License: The Code Project Open License (CPOL)

Beginners Guide To Threading In .NET Part 1 of n

By Sacha Barber

Beginners Guide To Threading In .NET
C# (C# 3.0, C#), .NET (.NET, .NET 3.5, .NET 2.0, .NET 3.0), WPF, Design, Dev

Posted: 17 May 2008
Updated: 9 Aug 2008
Views: 45,260
Bookmarked: 354 times
Announcements
Want a new Job?



Search    
Advanced Search
Sitemap
234 votes for this Article.
Popularity: 10.96 Rating: 4.63 out of 5
10 votes, 4.3%
1
0 votes, 0.0%
2
4 votes, 1.7%
3
29 votes, 12.4%
4
191 votes, 81.6%
5
Note: This is an unedited contribution. If this article is inappropriate, needs attention or copies someone else's work without reference then please Report This Article

Introduction

Im afraid to say that I am just one of those people that unless I am doing something, I am bored. So now that I finally feel I have learnt tha basics of WPF, it is time to turn my attention to other matters.

I have a long list of things that demand my attention such as (WCF/WF/CLR Via C# version 2 book), but I recently went for (and got, but turned it down in the end) which required me to know a lot about threading. Whilst I consider myself to be pretty good with threading, I thought yeah I'm ok at threading but I could always be better. So as a result of that I have decided to dedicate myself to writing a series of articles on threading in .NET. This series will undoubtely owe much to an excellent Visual Basic .NET Threading Handbook that I bought that is nicely filling the MSDN gaps for me and now you.

I suspect this topic will range from simple to medium to advanced, and it will cover a lot of stuff that will be in MSDN, but I hope to give it my own spin also.

I dont know the exact schedule, but it may end up being something like

I guess the best way is to just crack on. One note though before we start, I will be using C# and Visual Studio 2008.

What I'm going to attempt to cover in this article will be

I think thats quite enough for 1 article

What are Processes

When a user starts an Application, memory and a whole host of resources are allocated for the Application. The pysichal seperation of this memory and resources is called a Process. An Application may launch more than 1 Process. Its important to note that Applications and Processes are not the same thing at all.

You should all know that you can view the running Processes/Application in Windows using Task Manager.

Here is how many Application I had running

And here is the list of Processes, where it can be seen that there are many more Processes running. Applications may have one or more Processes involved, where each Process has its own seperation of data, execution code and system resources.

You might notice above that there is a reference to the CPU usage. This is down to the fact that each Process has an execution sequence used by the computers CPU. This execution sequence is known as a Thread. This Thread is defined by the registers in use on the CPU, the stack used by the Thread and a container that keeps track of the threads state (The Thread Local Storage TLS).

Creating a Process includes starting the Process running at a instruction point. This is normally known as the primary or main thread. This threads execution sequence is largely determined by how the user code is written.

Time Slices

With all these Processes all wanting a slice of the CPU time cycle how does it get managed. Well each Process is granted a slice of time (Quantum) on which it (the Process) may use the CPU. This slice of time should NEVER to considered a constant, it is effected by OS and CPU type.

Multithreaded Processes

What happens if we need our Process to do more than 1 thing, like query a web service and write to a database at the same time. Luckily we can split a Process to share the time slice allocated to it. This is done by spawning new threads in the current Process. These extra threads ares sometimes called worker threads. These worker threads share the processes memory space that is isolated from all other Processes on the system. The concept of spawning new threads within the same process is called free threading.

As some of you may (as I did) come from VB 6.0 where we had apartment threading, where each new thread was started in its own process and was granted its own data, so threads couldnt share data. Lets see some figures shall we as this is fairly important.

With this model each time you want to do some background work it happens in its own process, so was known as Out Of Process.

Free threading we can get the CPU to execut an additional thread using the same process data. This is much better than single threaded apartments, as we get all the added benifits of extra threads with the ability to share the same Process data.

NOTE : Only one thread actualy runs on the CPU at one time.

If we go back to Task Manager and change the view to include the thread count, we can see something like

This shows that each Process can clearly have more than 1 thread. So hows all this scheduling and state information managed. We will consider that next

Thread Local Storage

When a threads time slice has expired it doesnt just stop and wait its turn. Recall that a CPU can only run 1 thread at a time, so the current thread needs to be replaced with the next thread to get some CPU time. Before that happens the current thread needs to store its state information to allow it to execute properly again. This is what the TLS is all about. One of the registers stored in the TLS is the program counter, which tells the thread which instruction to execute next.

Interrupts

Processes dont need to know about each other to be shceduled correctly. Thats really the job of the operation system, even OS have a main thread sometimes called the system thread, which schedules all other threads. It does this by using interrupts. An interrupt is a mechanism that causes the normal execution flow to branch somewhere else in the computer memory without the knowledge of the execution program.

the OS determines how much time the thread has to execute and places and places an instruction in the current threads execution sequence. Since the interrupt is within the instruction set its a software interrupt, which isnt the same as a hardware interrupt.

Interrupts are a feature used in all but the simplest microprocessors to allow hardware devices to request attention. When an interrupt is received, a microprocessor will temporarily suspend execution of the code it was running and jump to a special program called an interrupt handler. The interrupt handler will typically service the device needing attention and then return to the previously-executing code.

One of the interrupts in all modern computers is controlled by a timer, whose function is to demand attention at periodic intervals. The handler will typically bump some counters, see if anything interesting is supposed to happen, and if there's nothing interesting (yet), return. Under Windows, one of the 'interesting' things that can happen is the expiry of a thread's time slice. When that occurs, Windows will force execution to resume in a different thread from the one that was interrupted.

Once an interrupt is placed the OS then allows the thread to execute. When the thread comes to the interrupt the OS uses a special function called an interrupt handler to store the threads state in the TLS. Once the thread time slice has timed out, it is moved to the end of the thread queue for its given priority (more on this later) to wait its turn again.

This is ok if the thread isn't done or needs to continue executing. What happens if the thread decides it does need any more CPU time just yet (maybe wait for a resource), so yeild its time slice to another thread.

This is down to the programmer and the OS. The programmer does the yield (normally using Sleep() method) the thread then clears any interrupts that teh OS may have placed in its stack. A software interrupt is then simulated. The thread is stored in the TLS and moved to the end of the queue as before.

The OS may have however already placed an interrupt in the threads stack, which must be cleared before the thread is packed away, otherwise when it executes again, it may get interrupted before it should be. The OS does this (thank goodness).

Thread Sleep And Clock Interrupts

As we just said a thread may decide to yield its CPU time to wait for a resource, but this could be 10 or 20 minutes, so the programmer may choose to make the thread sleep, which results in the thread being packed in the TLS. But it doesnt go to the runnable queue it goes to a sleep queue. In order for threads in the sleep queue to run again they need a different kind of interrupt, called a clock interrupt. When a thread enters the sleep queue a new clock interrupt is scheduled for the time that the thread when the thread should be awoken. When a clock interrupt occurs that matches an entry on the sleep queue the thread is moved back to the runnable queue.

Thread Abort / Thread done

All things have an end. When a thread is finished or it is programmatically aborted, the TLS for that thread is de-allocated. The data in the Process remains (remember its shared between all the Process threads, there could be more than 1), and will only be de-allocated when the Process itself is stopped.

So weve talked a bit about scheduling, but we also said the TLS stored state for threads, how does it do this. Well consider the following from MSDN

"Threads use a local store memory mechanism to store thread-specific data. The common language runtime allocates a multi-slot data store array to each process when it is created. The thread can allocate a data slot in the data store, store and retrieve a data value in the slot, and free the slot for reuse after the thread expires. Data slots are unique per thread. No other thread (not even a child thread) can get that data.

If the named slot does not exist, a new slot is allocated. Named data slots are public and can be manipulated by anyone."

Thats how in a nutshell. Lets see the MSDN example (Blatant steal here)

using System;
using System.Threading;

namespace TLSDataSlot
{
    class Program
    {
        static void Main()
        {
            Thread[] newThreads = new Thread[4];
            for (int i = 0; i < newThreads.Length; i++)
            {
                newThreads[i] =
                    new Thread(new ThreadStart(Slot.SlotTest));
                newThreads[i].Start();
            }
        }
    }


    class Slot
    {
        static Random randomGenerator = new Random();

        public static void SlotTest()
        {
            // Set different data in each thread's data slot.
            Thread.SetData(
                Thread.GetNamedDataSlot("Random"),
                randomGenerator.Next(1, 200));

            // Write the data from each thread's data slot.
            Console.WriteLine("Data in thread_{0}'s data slot: {1,3}",
                AppDomain.GetCurrentThreadId().ToString(),
                Thread.GetData(
                Thread.GetNamedDataSlot("Random")).ToString());

            // Allow other threads time to execute SetData to show
            // that a thread's data slot is unique to the thread.
            Thread.Sleep(1000);

            Console.WriteLine("Data in thread_{0}'s data slot is still: {1,3}",
                AppDomain.GetCurrentThreadId().ToString(),
                Thread.GetData(
                Thread.GetNamedDataSlot("Random")).ToString());

            // Allow time for other threads to show their data,
            // then demonstrate that any code a thread executes
            // has access to the thread's named data slot.        
            Thread.Sleep(1000);

            Other o = new Other();
            o.ShowSlotData();
            Console.ReadLine();
        }
    }

    public class Other
    {
        public void ShowSlotData()
        {
            // This method has no access to the data in the Slot
            // class, but when executed by a thread it can obtain
            // the thread's data from a named slot.
            Console.WriteLine(
                "Other code displays data in thread_{0}'s data slot: {1,3}",
                AppDomain.GetCurrentThreadId().ToString(),
                Thread.GetData(
                Thread.GetNamedDataSlot("Random")).ToString());
        }
    }
}

This may produce the following

It can be seen that this uses 2 things

  • GetNamedDataSlot : looks up a named slot
  • SetData : Sets the data in the specified slot within the current thread

There is another way, we can also use the ThreadStaticAttribute which means the value is unique for each thread. Lets see the MSDN example (Blatant steal here)

using System;
using System.Threading;

namespace ThreadStatic
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 3; i++)
            {
                Thread newThread = new Thread(ThreadData.ThreadStaticDemo);
                newThread.Start();
            }
        }
    }

    class ThreadData
    {
        [ThreadStaticAttribute]
        static int threadSpecificData;

        public static void ThreadStaticDemo()
        {
            // Store the managed thread id for each thread in the static
            // variable.
            threadSpecificData = Thread.CurrentThread.ManagedThreadId;

            // Allow other threads time to execute the same code, to show
            // that the static data is unique to each thread.
            Thread.Sleep(1000);

            // Display the static data.
            Console.WriteLine("Data for managed thread {0}: {1}",
                Thread.CurrentThread.ManagedThreadId, threadSpecificData);
        }
    }
}

And this may produce the following output

What Are AppDomains

When I talked about Processes earlier, I mentioned that Processes have physically isolated memory and resources needed to maintain themselves, and I also mentioned that a Process has at least 1 thread. Microsoft also introduced 1 extra layer of abstraction/isolation called an AppDomain. The AppDomain is not a physical isolation, but rather a logic isolation within the Process. Since more than 1 AppDomain can exist in Process we get some benifits. For example until we had an AppDomain Processes that needed to access each others data had to use a Proxy, which introduced extra code and overhead. By using a AppDomain it is possible to launch several applications within the same Process. The same sort of isolation that exists with Processes is also available for AppDomain.Threads can execure across application domains without the overhead of inter process communication. This is all encapsulated within the AppDomain class. Any time a namespace is loaded in an application it is loaded into an AppDomain. The AppDomain used will be the same as the calling code unless otherwise specified. AppDomain may or may not contain threads, which is different to Processes.

Why You Should Use AppDomains

As I stated above AppDomains are a further level of abstraction/isolation, and they sit within a Process. So why use AppDomains? One of this articles readers actually gave a very good example to this question.

"I have previously needed to execute code in a separate AppDomain for a Visual Studio AddIn that used reflection to look at the current project's DLL file. Without examining the DLL in a separate AppDomain, any changes to the project made by the developer would not show up in reflection unless they restarted Visual Studio. This is exactly because of the reason pointed out by Marc: once an AppDomain loads an assembly, it can't be unloaded."

AppDomain forum post, by Daniel Flowers

So we can see that an AppDomain can be use to load and Assembly dynamically, and the entire AppDomain can be destroyed, without affecting the Process. I think this illustrates the abtraction/isolation that an AppDomain gives us.

NUnit also takes this approach, but more on this below.

Setting AppDomain Data

Lets see an example of how to work with AppDomain data

using System;
using System.Threading;

namespace AppDomainData
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Fetching current Domain");
            //use current AppDomain, and store some data
            AppDomain domain = System.AppDomain.CurrentDomain;
            Console.WriteLine("Setting AppDomain Data");
            string name = "MyData";
            string value = "Some data to store";
            domain.SetData(name, value);
            Console.WriteLine("Fetching Domain Data");
            Console.WriteLine("The data found for key {0} is {1}",
                name, domain.GetData(name));
            Console.ReadLine();
        }
    }
}

This produces the rather unexciting output

And how about executing code in a specific AppDomain, let see that now

using System;
using System.Threading;

namespace LoadNewAppDomain
{
    class Program
    {
        static void Main(string[] args)
        {
            AppDomain domainA = AppDomain.CreateDomain("MyDomainA");
            AppDomain domainB = AppDomain.CreateDomain("MyDomainB");
            domainA.SetData("DomainKey", "Domain A value");
            domainB.SetData("DomainKey", "Domain B value");
            OutputCall();
            domainA.DoCallBack(OutputCall); //CrossAppDomainDelegate call
            domainB.DoCallBack(OutputCall); //CrossAppDomainDelegate call
            Console.ReadLine();
        }

        public static void OutputCall()
        {
            AppDomain domain = AppDomain.CurrentDomain;
            Console.WriteLine("the value {0} was found in {1}, running on thread Id {2}",
                domain.GetData("DomainKey"),domain.FriendlyName,
                Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }
}

NUnit And AppDomains

Since I first published this article there has been a few suggestions, the one that seemed to get the most attention (at least for this articles content) was NUnit and AppDomains, so I thought I better address that.

Shown below are to interesting quotes that I found on the NUnit site and also a personal blog.

"Dynamic reloading of an assembly using AppDomains and shadow copying. This also applies if you add or change tests. The assembly will be reloaded and the display will be updated automatically. The shadow copies use we use a configurable directory specified in the executable’s (nunit-gui and nunit-console) config files."

NUnit Release Notes Page

"NUnit was written by .NET Framework experts. If you look at the NUnit source, you see that they knew how to dynamically create AppDomains and load assemblies into these domains. Why is a dynamic AppDomain important? What the dynamic AppDomain lets NUnit do is to leave NUnit open, while permitting you to compile, test, modify, recompile, and retest code without ever shutting down. You can do this because NUnit shadow copies your assemblies, loads them into a dynamic domain, and uses a file watcher to see if you change them. If you do change your assemblies, then NUnit dumps the dynamic AppDomain, recopies the files, creates a new AppDomain, and is ready to go again."

Dr Dobbs Portal

Essentially what NUnit does is host the test Assembly in a seperate AppDomain. And as AppDomains are isolated, they can be unloaded without affecting the Process to which they belong.

Thread Priorities

Just as in real life we as human have priorities, so to do Threads. A programmer can decide a priority for their Thread, but ultimately its up the recipient to decide what should be acted upon now, and what can wait.

Windows uses a priority system from 0-31 where 31 is the higest. Anything higher than 15 needs to be done via an Administrator. Threads that have priority between 16-31 are considered real time and will pre-empt lower priority level threads. Think about drivers/input devices and things like this, these will be running with priorities between 16-31.

In Windows there is a sceduling system (typically round robin) where each priority has a queue of threads. ALL threads with the highest priority are allocated some CPU time, then the next level (lower down) are allocated some time and so on. If a new thread appears with a higher priority then the current thread is pre-empted and the new higher level priority level thread is run. Lower level priority threads will only be scheduled if there are no higher level threads in other priority queues.

If we again use Task Manager we can see that it is possible to alter a Process to have a higher priority that will enable any new spawned threads a higher possibility of being scheduled (given some CPU time).

But we also have options when we use code, as the System.Threading.Thread class exposes a Priority property. If we look at what MSDN states, we could set on of the following:

A thread can be assigned any one of the following priority values:

  • Highest
  • AboveNormal
  • Normal
  • BelowNormal
  • Lowest

NOTE : Operating systems are not required to honor the priority of a thread.

For instance, a OS may decay the priority assigned to a high-priority thread, or otherwise dynamically adjust the priority in the interest of fairness to other threads in the system. A high-priority thread can, as a consequence, be preempted by threads of lower priority. In addition, most OSs have unbounded dispatch latencies: the more threads in the system, the longer it takes for the OS to schedule a thread for execution. Any one of these factors can cause a high-priority thread to miss its deadlines, even on a fast CPU.

The same could be said for programatically setting a user created Thread to be set at a Highest level priority. So be warned, be careful what you do when setting priorities for Threads.

Starting Threads

Starting new Threads is pretty easy, we just need to use one of the Thread constructors, such as

  • Thread(ThreadStart)
  • Thread(ParameterizedThreadStart)

There are others, but these are the most common ways to start threads. Lets look at an example of each of these

No Parameters

Thread workerThread = new Thread(StartThread);
Console.WriteLine("Main Thread Id {0}",
    Thread.CurrentThread.ManagedThreadId.ToString());
workerThread.Start();

....
....
public static void StartThread()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("Thread value {0} running on Thread Id {1}",
            i.ToString(), 
            Thread.CurrentThread.ManagedThreadId.ToString());
    }
}

Single Parameter

//using parameter
Thread workerThread2 = new Thread(ParameterizedStartThread);
// the answer to life the universe and everything, 42 obviously
workerThread2.Start(42); 
Console.ReadLine();

....
....
public static void ParameterizedStartThread(object value)
{
    Console.WriteLine("Thread passed value {0} running on Thread Id {1}",
        value.ToString(),
        Thread.CurrentThread.ManagedThreadId.ToString());
}

Putting it all together we can see a small program with a main thread and 2 worker threads.

using System;
using System.Threading;

namespace StartingThreads
{
    class Program
    {
        static void Main(string[] args)
        {
            //no parameters
            Thread workerThread = new Thread(StartThread);
            Console.WriteLine("Main Thread Id {0}",
                Thread.CurrentThread.ManagedThreadId.ToString());
            workerThread.Start();

            //using parameter
            Thread workerThread2 = new Thread(ParameterizedStartThread);
            // the answer to life the universe and everything, 42 obviously
            workerThread2.Start(42); 
            Console.ReadLine();
        }

        public static void StartThread()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Thread value {0} running on Thread Id {1}",
                    i.ToString(), 
                    Thread.CurrentThread.ManagedThreadId.ToString());
            }
        }

        public static void ParameterizedStartThread(object value)
        {
            Console.WriteLine("Thread passed value {0} running on Thread Id {1}",
                value.ToString(),
                Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }
}

Which may produce something like

Pretty simpe huh.

Callbacks

So you have now seen some simple examples of creating threads.

What we have not seen yet is the principle of Synchronization between threads.

Threads run out of sequence from the rest of the application code, so you can never be sure of the exact order of events. That is to say, we can't garuantee that actions that effect a shared resources used by one thread, will be completed before code is run in another thread.

We will be looking at this in great detail in subsequent articles, but for now let's consider a small example using a Timer. Using a Timer we can specify that a method is called at some interval and this could check the state of some data before continuing. Its a very simply model, the next articles will show more detail about more advanced Synchronization techniques, but for now lets just use a Timer.

Lets see a very small example. This example starts a worker thread, and a Timer. The main thread is put in a loop waiting for a completed flag to be set "true". The Timer is waiting for a message from the worker thread of "Completed" before allowing the blocked main thread to continue by setting the completed flag to "true".

using System;
using System.Threading;


namespace CallBacks
{
    class Program
    {
        private string message;
        private static Timer timer;
        private static bool complete;


        static void Main(string[] args)
        {
            Program p = new Program();
            Thread workerThread = new Thread(p.DoSomeWork);
            workerThread.Start();

            //create timer with callback
            TimerCallback timerCallBack =
                new TimerCallback(p.GetState);
            timer = new Timer(timerCallBack, null, 
                TimeSpan.Zero, TimeSpan.FromSeconds(2));

            //wait for worker to complete
            do
            {
                //simply wait, do nothing
            } while (!complete);

            Console.WriteLine("exiting main thread");
            Console.ReadLine();
        }


        public void GetState(Object state)
        {
            //not done so return
            if (message == string.Empty) return;
            Console.WriteLine("Worker is {0}", message);
            //is other thread completed yet, if so signal main
            //thread to stop waiting
            if (message == "Completed")
            {
                timer.Dispose();
                complete = true;
            }
        }

        public void DoSomeWork()
        {
            message = "processing";
            //simulate doing some work
            Thread.Sleep(3000); 
            message = "Completed";
        }
    }
}

This may produce something like

We're Done

Well thats all I wanted to say this time. Threading is a complex subject, and as such this series will be quite hard, but I think worth a read.

Next Time

Next time we will be looking at Lifecycle Of Threads/Threading Opportunities/Traps.

Could I just ask, if you liked this article could you please vote for it, as that will tell me whether this Threading crusade that I am about to embark on will be worth creating articles for.

I thank you very much

History

v1.1 19/05/08 : Added why to use AppDomain/NUnit with AppDomain, and extra parts to threading priorities

v1.0 18/05/08 : Initial issue

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Sacha Barber


Mvp
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

Occupation: Software Developer (Senior)
Location: United Kingdom United Kingdom

Other popular Threads, Processes & IPC articles:

Article Top
Sign Up to vote for this article
You must Sign In to use this message board.
FAQ FAQ Noise ToleranceSearch Search Messages 
 Layout  Per page   
 Msgs 1 to 25 of 153 (Total in Forum: 153) (Refresh)FirstPrevNext
Subject  Author Date 
QuestionArticle is similar to the book C# Threading Hand BookmemberMember 7297723:04 11 Aug '08  
AnswerRe: Article is similar to the book C# Threading Hand Book [modified]mvpSacha Barber3:54 11 Aug '08  
GeneralRe: Article is similar to the book C# Threading Hand BookmemberMember 72977219:27 11 Aug '08  
GeneralRe: Article is similar to the book C# Threading Hand BookmvpSacha Barber22:57 11 Aug '08  
GeneralGreat Article!memberJason Song16:45 9 Aug '08  
GeneralRe: Great Article!mvpSacha Barber22:13 9 Aug '08  
GeneralGreat Read - Nice examplesmemberPCoffey10:31 17 Jul '08  
GeneralRe: Great Read - Nice examplesmvpSacha Barber0:17 18 Jul '08  
QuestionInterrupts not very clearmemberPrathmesh Chandra Kandpal1:19 8 Jul '08  
AnswerRe: Interrupts not very clearmemberMr MM5:01 15 Jul '08  
AnswerRe: Interrupts not very clearmembersupercat910:13 20 Jul '08  
GeneralRe: Interrupts not very clearmvpSacha Barber10:43 20 Jul '08  
GeneralRe: Interrupts not very clearmembersupercat917:19 20 Jul '08  
GeneralRe: Interrupts not very clearmvpSacha Barber2:40 22 Jul '08  
GeneralRe: Interrupts not very clearmemberMr MM22:04 23 Jul '08  
GeneralRe: Interrupts not very clearmvpSacha Barber22:46 23 Jul '08  
GeneralNice seriesmemberSyed Mehroz Alam2:29 26 Jun '08