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 |
|---|---|
|
The lowest priority |
|
Below normal |
|
Normal (default) |
|
Above normal |
|
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 |
|---|---|---|---|
|
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.
GO TO FULL VERSION