1. Computing

Multi-threading in C# with Tasks Tutorial

Using the Task Parallel Library in .NET 4.0

By

Screenshot of Folders Application
How to Do Multi-Threading in C#

The whole multi-tasking, multi-threading issue can get a little confusing so here are some simple definitions.

An application has one or more processes in it. Think of a process as a program running on your computer. Now each process has one or more threads. A game application might have a thread to load resources from disk, another to do AI, another to run the game as a server and so on.

What is a thread? It's short for thread of execution. The processor follows a path through your code.

In .NET/Windows the operating system allocates processor time to a thread. Each thread keeps track of exception handlers, the priority at which it runs and has somewhere to save the thread context until it runs. Thread context is the information that the thread needs to resume and includes the stack and the values in the CPU registers.

In the previous tutorial on Background Workers, we were able to run a background task without getting hung up on the complexities of threads. In this tutorial though I'll show enough to get you started on using threads and introduce tasks.

Good and Bad with Threads

Threads take up a bit of memory (about 1MB each) and creating them takes a lttle time so generally you don't actually want to use too many. Remember they are competing for processor time; if your computer has multiple CPUs (cores) then Windows or .NET might run each thread on a different CPU but if several threads run on the same CPU then only one can be active at a time and switching threads takes time.

It can be helpful to think in a CPU centric way. The CPU runs a thread for a few million instructions then it switches to another thread. All of the CPU registers, current program execution point and stack have to be saved somewhere for the first thread and then restored from somewhere else for the next thread and so on.

Creating a Thread

In the namespace System.Threading you'll find the Thread type. The constructor Thread( ThreadStart ) creates an instance of a Thread. However in more recent C# code it's more likely to pass in a Lambda expression that calls the method with any parameters.

If you're unsure about Lambda expressions, it might be worth checking out LINQ.

using System;
using System.Threading;

namespace ex1
{
    class Program
    {

        public static void Write1()
        {
            Console.Write('1') ;
            Thread.Sleep(500) ;
        }

        static void Main(string[] args)
        {
            var task = new Thread(Write1) ;
            task.Start() ;
            for (var i = 0; i < 10; i++)
            {
                Console.Write('0') ;
                Console.Write (task.IsAlive ? 'A' : 'D') ;
                Thread.Sleep(150) ;
            }
            Console.ReadKey() ;
        }
    }
}

In that example, a thread is created and started. All it does is write 1 to the console. The main thread writes a 0 to the console ten times, each time followed by an A or D depending on whether the other thread is still Alive or Dead.

The other thread only runs once and writes a '1'. After the half second delay in the Write1() thread, the thread finishes and the Task.IsAlive in the main loop now returns 'D'.

Thread Pool

Instead of creating your own Thread, unless you really need to do it, why not make use of a Thread Pool. That's what Background Workers do.

From .NET 4.0, we have access to the Task Parallel Library (TPL). As we saw in the last example, again we need a bit of LINQ, and yes it's all Lambda Expressions.

Tasks uses the Thread Pool behind the scenes, but makes better use of the threads depending on the number in use, plus you can get better control of Tasks than with a thread.

The main object in the TPL is a Task. This is a class that represents an Asynchronous operation; ie after starting it, the code continues, it doesn't block waiting for it to complete.

The commonest way to start things running is with the Task.Factory.StartNew as in:

Task.Factory.StartNew(() => DoSomething());

Where DoSomething() is the method that is run. It's possible to create a Task and not have it run immediately. In that case, just use Task like this

var t = new Task(() => Console.WriteLine("Hello"));
...
t.Start();

That doesn't start the thread until the .Start() is called. In the example below, I create five tasks and run each.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ex1
{
    class Program
    {

        public static void Write1(int i)
        {
            Console.Write(i) ;
            Thread.Sleep(50) ;
        }

        static void Main(string[] args)
        {
            
            for (var i = 0; i < 5; i++)
            {
                var value = i;
                var runningTask = Task.Factory.StartNew(()=>Write1(value)) ;
            }
            Console.ReadKey() ;
        }
    }
}

Run that and you'll get the digits 0-4 output in some random order like 03214. That's because the order of task execution is done by .NET.

You might be wondering why the var value = i is needed. Try removing it and calling Write(i) and you'll see something unexpected like 55555. Why is this? It's because the Task shows the value of i at the time that the task is executed, not when the task was created. By creating a new variable each time in the loop, each of the five values is correctly stored and picked up.

This has been a brief intro into Tasks and the TPL and I'll continue using tasks with the next tutorial.

©2014 About.com. All rights reserved.