CodeGym /Courses /C# SELF /Thread priorities and thread types

Thread priorities and thread types

C# SELF
Level 55, Lesson 3
Available

1. Introduction

Let's imagine threads are people in a line for ice cream. Some stand quietly, others make a fuss ("My train's leaving soon, let me go!"), and the seller seems to hear everyone but sometimes serves someone out of order — like a mom with a crying kid. Thread priorities are kinda the same story.

In C# (more precisely, in .NET) every thread has a priority — a hint to the OS about how "important" that thread is compared to others. It's not a strict rule for the OS (nobody guarantees the highest-priority thread will always get all the attention), but usually higher-priority threads get more CPU time.

Where is this actually needed?

  • In user interfaces: for example, screen updates shouldn't be blocked by long, low-priority computations.
  • In games: rendering a frame is more important than background map generation.
  • In time-critical tasks: device control, signal processing, etc.

2. Thread priorities: how it works

In C# it's simple to work with thread priority — the Thread object has a Priority property. It accepts values from the ThreadPriority enum:

Value Description
Lowest
The lowest priority
BelowNormal
Below normal
Normal
Normal (default)
AboveNormal
Above normal
Highest
The highest priority

Code example:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread lowPriorityThread = new Thread(PrintLowPriority);
        Thread highPriorityThread = new Thread(PrintHighPriority);

        lowPriorityThread.Priority = ThreadPriority.Lowest;
        highPriorityThread.Priority = ThreadPriority.Highest;

        lowPriorityThread.Start();
        highPriorityThread.Start();
    }

    static void PrintLowPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Low priority: " + i);
            Thread.Sleep(10); // Let's add a pause to see the difference
        }
    }

    static void PrintHighPriority()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("High priority: " + i);
            Thread.Sleep(10);
        }
    }
}

Note

In practice, especially on modern multi-core systems and in the managed .NET environment, priority is just a recommendation to the OS. The OS (and .NET) will try to give more time to higher-priority threads, but there are no ironclad guarantees. For example, if your thread takes the whole CPU at Highest, the UI may start to lag and other threads will suffer.

3. Changing thread priorities

Threads and priorities

graph LR
    A[Thread 1 - Lowest] -->|Less CPU time| OS(Operating system)
    B[Thread 2 - Normal] --> OS
    C[Thread 3 - Highest] -->|More CPU time| OS
    OS --> CPU(Processor)

Why change a thread's priority?

It might seem like you could always set Highest, but that's a bad idea! Imagine if all customers in a store started shouting "Serve me first!" at the same time.

Change priority only when justified:

  • A background logging thread — you can make it BelowNormal or Lowest.
  • A thread handling user input — AboveNormal.
  • CPU-heavy tasks that aren't time-critical — low priority.

Pro tip: If the app starts lagging, check for "rude" threads with high priority that steal time from others!

4. Thread categories in .NET

In C# we usually distinguish two types of threads: foreground and background. Surprisingly, "background" isn't necessarily the one doing "background work". Let's clarify.

Foreground threads

  • By default, threads you create are foreground.
  • As long as at least one foreground thread is alive, the application won't exit, even if everything else is "dead".
  • Example: the main program thread (Main), and threads created via new Thread() without changes.

Background threads

  • If all foreground threads finish and only background thread(s) remain — the process terminates and those threads are aborted without warning.
  • Used for secondary tasks: logging, background metrics sending, etc.
  • To make a thread background set IsBackground to true:
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();

Demonstration of the difference

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread backgroundThread = new Thread(() =>
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("Background thread is working... " + i);
                Thread.Sleep(500);
            }
            Console.WriteLine("Background thread FINISHED.");
        });

        backgroundThread.IsBackground = true; // Make the thread background!

        backgroundThread.Start();

        Console.WriteLine("Main will finish in 1 second.");
        Thread.Sleep(1000); // Wait one second
        Console.WriteLine("Main finished. What will happen to the background thread?");
    }
}

What you'll see?
Main may finish and the background thread will be "killed" halfway. This is convenient for tasks that shouldn't block app shutdown.

5. Thread types

ThreadPool

  • ThreadPool is a mechanism that manages a pool of threads so you don't create too many of them.
  • It's used when there are many short tasks: parallel request handling, asynchronous operations.
  • Threads from the pool are always background — they won't block app exit.

Example: running code in the thread pool

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "background task");
        Console.WriteLine("Main is finishing.");
        Thread.Sleep(500);
    }

    static void DoWorkInThreadPool(object? state)
    {
        Console.WriteLine("Thread from pool: " + state);
        Thread.Sleep(1000); // Let's try to sleep
        Console.WriteLine("Thread from pool finished!");
    }
}

Note:
If Main finished earlier, the pool thread could be aborted. If you need a guarantee of completion — use foreground threads or wait explicitly.

The evolution of multithreading: tasks, async/await

In modern apps you rarely create threads manually (Thread). You typically use Task and async methods. Important things to know:

  • ThreadPool threads and Task usually run on background threads.
  • For most scenarios you don't need to change priority — stick to common sense and best practices.

6. Useful nuances

Thread properties and behavior

Property Foreground Thread Background Thread ThreadPool Thread
IsBackground
false by default true true
Application shutdown The app does NOT exit while at least one foreground thread is alive The app will exit if all foreground threads finished The app will exit as soon as Main finishes
Control Full control Full control No direct control
Typical use Long, important tasks (e.g., DB server) Non-critical tasks, background operations, logging Short tasks, Task, async operations
Priority Can be set Can be set Not recommended to change

Non-obvious tips and remarks

  • You can change priority only for regular Thread instances, not for pool tasks (Task, ThreadPool).
  • Threads from the pool always have Normal priority (can't change).
  • Task and async/await are the modern approach where priority and background details are mostly hidden "behind the scenes".

7. Common mistakes with priorities and thread types

Mistake #1: overusing priorities.
Setting everyone to Highest or everyone to Lowest without a good reason doesn't help. It can break the execution balance and reduce app responsiveness.

Mistake #2: implicit termination of background threads.
If important work (like saving data) runs on a background thread and you don't wait for it to finish, you risk losing data. Background threads are aborted automatically at process exit.

Mistake #3: expecting too much from thread priority.
Thread priority (Priority) is only a recommendation to the OS, not a guarantee that the thread will run first.

2
Task
C# SELF, level 55, lesson 3
Locked
Setting and Checking Thread Priority
Setting and Checking Thread Priority
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION