C Sharp

Scheduling Threads

When the processor switches between threads once a given thread's timeslice has ended, the process of choosing which thread executes next is far from arbitrary. Each thread has an associated priority level that tells the processor how it should be scheduled in relation to the other threads in the system. This priority level is defaulted to Normal-more on this shortly-for threads that are created within the runtime. For threads that are created outside the runtime, they retain their original priority. You use the Thread.Priority property to view and set this value. The Thread.Priority property's setter takes a value of type Thread.ThreadPriority that is an enum that defines these values: Highest, AboveNormal, Normal, BelowNormal, and Lowest.

To illustrate how priorities can affect even the simplest code, take a look at the following example in which one worker thread counts from 1 to 10 and the other counts from 11 to 20. Note the nested loop within each WorkerThread method. Each loop is there to represent work the thread would be doing in a real application. Because these methods don't really do anything, not having those loops would result in each thread finishing its work in its first timeslice! -

using System;
using System.Threading;
class ThreadSchedule1App
{
    public static void WorkerThreadMethod1()
    {
        Console.WriteLine("Worker thread started");
        Console.WriteLine
           ("Worker thread - counting slowly from 1 to 10");
        for (int i = 1; i < 11; i++)
        {
            for (int j = 0; j < 100; j++)
            {
                Console.Write(".");
                // Code to imitate work being done.
                int a;
                a = 15;
            }
            Console.Write("{0}", i);
        }
        Console.WriteLine("Worker thread finished");
    }
    public static void WorkerThreadMethod2()
    {
        Console.WriteLine("Worker thread started");
        Console.WriteLine
           ("Worker thread - counting slowly from 11 to 20");
        for (int i = 11; i < 20; i++)
        {
            for (int j = 0; j < 100; j++)
            {
                Console.Write(".");
                // Code to imitate work being done.
                int a;
                a = 15;
            }
            Console.Write("{0}", i);
        }
        Console.WriteLine("Worker thread finished");
    }
    public static void Main()
    {
        ThreadStart worker1 = new ThreadStart(WorkerThreadMethod1);
        ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
        Console.WriteLine("Main - Creating worker threads");
        Thread t1 = new Thread(worker1);
        Thread t2 = new Thread(worker2);
        t1.Start();
        t2.Start();
    }
}

Running this application results in the following output. Note that in the interest of brevity I've truncated the output-most of the dots are removed.

Main - Creating worker threads
Worker thread started
Worker thread started
Worker thread - counting slowly from 1 to 10
Worker thread - counting slowly from 11 to 20
......1......11......2......12......3......13

As you can see, both threads are getting equal playing time with the processor. Now alter the Priority property for each thread as in the following code-I've given the first thread the highest priority allowed and the second thread the lowest-and you'll see a much different result.

using System;
using System.Threading;
class ThreadSchedule2App
{
    public static void WorkerThreadMethod1()
    {
        Console.WriteLine("Worker thread started");
        Console.WriteLine
            ("Worker thread - counting slowly from 1 to 10");
        for (int i = 1; i < 11; i++)
        {
            for (int j = 0; j < 100; j++)
            {
                Console.Write(".");
                // Code to imitate work being done.
                int a;
                a = 15;
            }
            Console.Write("{0}", i);
        }
        Console.WriteLine("Worker thread finished");
    }
    public static void WorkerThreadMethod2()
    {
        Console.WriteLine("Worker thread started");
        Console.WriteLine
            ("Worker thread - counting slowly from 11 to 20");
        for (int i = 11; i < 20; i++)
        {
            for (int j = 0; j < 100; j++)
            {
                Console.Write(".");
                // Code to imitate work being done.
                int a;
                a = 15;
            }
            Console.Write("{0}", i);
        }
        Console.WriteLine("Worker thread finished");
    }
    public static void Main()
    {
        ThreadStart worker1 = new ThreadStart(WorkerThreadMethod1);
        ThreadStart worker2 = new ThreadStart(WorkerThreadMethod2);
        Console.WriteLine("Main - Creating worker threads");
        Thread t1 = new Thread(worker1);
        Thread t2 = new Thread(worker2);
        t1.Priority = ThreadPriority.Highest;
        t2.Priority = ThreadPriority.Lowest;
        t1.Start();
        t2.Start();
    }
}

The (abbreviated) results should be as shown below. Notice that the second thread has been set to such a low priority that it doesn't receive any cycles until after the first thread has completed its work.

Main - Creating worker threads
Worker thread started
Worker thread started
Worker thread - counting slowly from 1 to 10
Worker thread - counting slowly from 11 to 20
......1......2......3......4......5......6......7......8......9......10......
11......12......13......14......15......16......17......18......19......20

Remember that when you tell the processor the priority you want a given thread to have, it's the operating system that eventually uses this value as part of its scheduling algorithm that it relays to the processor. In .NET, this algorithm is based on the priority level that you've just used (with the Thread.Priority property) as well as the process's priority class and dynamic boost values. All these values are used to create a numeric value ( 0-31 on an Intel processor) that represents the thread's priority. The thread having the highest value is the thread with the highest priority.

One last note on the subject of thread scheduling: use it with caution. Let's say you have a GUI application and a couple of worker threads that are off doing some sort of asynchronous work. If you set the worker thread priorities too high, the UI might become sluggish because the main thread in which the GUI application is running is receiving fewer CPU cycles. Unless you have a specific reason to schedule a thread with a high priority, it's best to let the thread's priority default to Normal.

NOTE
When you have a situation where several threads are running at the same priority, they'll all get an equal amount of processor time. This is called round robin scheduling.