# Threading

Course: [C# Advanced](https://www.devtutorials.ai/learn/advanced-csharp) · Section: Multithreading
Source: https://www.devtutorials.ai/learn/advanced-csharp/multithreading-parallel/threading

> Threads and processes, creating threads, the lifecycle, foreground and background threads, the thread pool, Task.Run, CPU vs I/O work and error handling.

## Introduction to Threads

Imagine a small shop with one cashier.

Customers queue up, and the cashier serves them one at a time. If one customer takes ten minutes to count coins, everyone behind them waits.

Now the shop opens a second and a third till. Three cashiers serve three customers **at the same time**. The queue moves much faster.

In a program, each cashier is a **thread**.

**In simple words:** a thread is a worker inside your program that runs code. More threads means more code can run at the same time.

Every C# program starts with one thread, the **main thread**. It runs `Main` (or your top-level statements) from top to bottom. You can create more threads to do work alongside it.

**Easy Way to Remember:** a **program** is the shop. A **thread** is a cashier. More cashiers serve more customers at once, as long as there are enough tills (CPU cores).

## What You Will Learn

1. What a thread is, and how it differs from a process.
2. How to create and start threads, and the thread lifecycle.
3. Foreground and background threads, and the `Thread` class.
4. The thread pool and `Task.Run`.
5. When to use the thread pool and when to create threads yourself.
6. CPU-bound versus I/O-bound work.
7. How to manage many workers and handle their exceptions.
8. How to choose between threads, tasks, and async.

**About the Run buttons in this lesson.** The examples run in your browser on .NET in WebAssembly, which has **only one thread**. Creating a `Thread` there throws `PlatformNotSupportedException`, so those examples are shown as plain code to run on your own computer. `Task.Run` does work in the browser: the work waits its turn and runs on the same thread. The results are the same; only the timing is different from a multi-core PC.

## Start With a Working Example

Press **Run**.

```csharp runnable title="Three workers share the job"
using System;
using System.Threading.Tasks;

Console.WriteLine("Main: handing out work");

Task<int> worker1 = Task.Run(() => SumRange(1, 1_000));
Task<int> worker2 = Task.Run(() => SumRange(1_001, 2_000));
Task<int> worker3 = Task.Run(() => SumRange(2_001, 3_000));

int[] parts = await Task.WhenAll(worker1, worker2, worker3);

Console.WriteLine($"Part totals: {string.Join(", ", parts)}");
Console.WriteLine($"Grand total: {parts[0] + parts[1] + parts[2]}");

static int SumRange(int from, int to)
{
    int total = 0;
    for (int i = from; i <= to; i++) total += i;
    return total;
}
```

The big job (add up 1 to 3,000) is split into three smaller jobs. `Task.Run` hands each one to a worker thread. On a PC with several cores, the three parts really run at the same time.

### Try This

Add a fourth worker for `3_001` to `4_000`, and include it in `WhenAll` and the grand total. The new grand total is `8002000`.

## Process vs Thread

A **process** is a running program. When you open a browser, a text editor, and a music player, the computer runs three processes.

A **thread** lives **inside** a process. One process can have many threads, and they all share the process's memory.

| | Process | Thread |
|---|---|---|
| What it is | A running program | A worker inside a program |
| Memory | Its own, separate memory | Shares memory with the other threads in the same process |
| Cost to start | High (milliseconds) | Low (microseconds) |
| If it crashes | Other processes keep running | The whole process stops |
| Talking to others | Hard (files, pipes, network) | Easy (they share variables) |
| Example | Your browser | One tab loading a page while another plays video |

**In simple words:** a process is a house. Threads are the people living in it. They share the kitchen, so they can help each other, but they can also get in each other's way.

That last point is important. Because threads share memory, two threads can change the same variable at the same time. Lesson 5 shows how to keep that safe.

```csharp runnable title="Information about the current process and thread"
using System;
using System.Threading;

Console.WriteLine($"CPU cores available: {Environment.ProcessorCount > 0}");
Console.WriteLine($"This code runs on a thread: {Thread.CurrentThread.ManagedThreadId > 0}");
Console.WriteLine($"It is the main (foreground) thread: {!Thread.CurrentThread.IsBackground}");
```

Each thread has a **`ManagedThreadId`**, a number that identifies it. `Environment.ProcessorCount` tells you how many CPU cores the program can use, which is how many threads can truly run at the same moment.

## Creating Threads

The classic way to create a thread is with the **`Thread`** class:

1. Create a `Thread` and give it the method to run.
2. Call **`Start()`** to begin.
3. Call **`Join()`** to wait for it to finish.

```text
using System;
using System.Threading;

Thread worker = new Thread(PrintNumbers);
worker.Start();                 // the worker begins running

for (int i = 1; i <= 3; i++)
    Console.WriteLine($"Main: {i}");

worker.Join();                  // wait until the worker is done
Console.WriteLine("Both finished");

static void PrintNumbers()
{
    for (int i = 1; i <= 3; i++)
        Console.WriteLine($"  Worker: {i}");
}
```

One possible output:

```text
Main: 1
  Worker: 1
Main: 2
  Worker: 2
  Worker: 3
Main: 3
Both finished
```

The lines from the two threads are **mixed**, and the mix changes every time you run it. Both threads run at the same time, and the operating system decides who goes next. Only "Both finished" is always last, because `Join` waits for the worker.

To pass data into a thread, use a lambda:

```text
string file = "report.pdf";
var t = new Thread(() => Console.WriteLine($"Printing {file}"));
t.Start();
t.Join();
```

`new Thread(...)` cannot run in this browser playground; it throws `PlatformNotSupportedException`. Copy these examples into a console app on your own computer to see real threads. In modern code, you rarely create threads yourself anyway: `Task.Run` (below) is the usual choice.

## Thread Lifecycle

A thread moves through a few **states** during its life. You can read the current state with the `ThreadState` property.

| State | Meaning |
|---|---|
| `Unstarted` | Created, but `Start()` has not been called yet |
| `Running` | Running code (or ready to run) |
| `WaitSleepJoin` | Paused: sleeping, waiting for a lock, or inside `Join()` |
| `Stopped` | Finished. It cannot be started again |

```text
Unstarted --Start()--> Running <--> WaitSleepJoin
                          |
                    method ends
                          v
                       Stopped
```

```text
var t = new Thread(() => Thread.Sleep(200));
Console.WriteLine(t.ThreadState);   // Unstarted
t.Start();
Thread.Sleep(50);
Console.WriteLine(t.ThreadState);   // WaitSleepJoin (it is sleeping)
t.Join();
Console.WriteLine(t.ThreadState);   // Stopped
```

A thread can only be started **once**. Calling `Start()` on a stopped thread throws `ThreadStateException`. To run the work again, create a new thread.

## Foreground and Background Threads

Every thread is either a **foreground** or a **background** thread.

- A **foreground** thread keeps the program alive. The program does not exit until every foreground thread has finished.
- A **background** thread does **not** keep the program alive. When the last foreground thread ends, background threads are stopped immediately, even in the middle of their work.

Think of a cinema. The film (foreground) decides when the show ends. The cleaner (background) stops working as soon as the doors close, even if they have not finished.

| | Foreground | Background |
|---|---|---|
| Keeps the program running | Yes | No |
| Default for `new Thread(...)` | Yes | No |
| Thread-pool threads (`Task.Run`) | No | **Yes** |
| Set with | `IsBackground = false` | `IsBackground = true` |
| Good for | Work that must finish, like saving a file | Helpers that can stop anytime, like a clock or a spell checker |

```text
var saver = new Thread(SaveFile) { IsBackground = false };   // must finish
var ticker = new Thread(ShowClock) { IsBackground = true };  // may stop anytime
saver.Start();
ticker.Start();
// When Main ends, the program waits for saver, but not for ticker.
```

Thread-pool threads are always background threads. On your own computer, this prints `True`:

```text
bool isBackground = await Task.Run(() => Thread.CurrentThread.IsBackground);
Console.WriteLine($"Task.Run thread is background: {isBackground}");
```

## Thread Class

The **`Thread`** class has several members you will use often.

| Member | What it does |
|---|---|
| `new Thread(method)` | Creates a thread that will run `method` |
| `Start()` | Begins running the thread |
| `Join()` | Waits until the thread finishes |
| `IsBackground` | Foreground or background |
| `Name` | A name, useful in the debugger |
| `ThreadState` | The current state |
| `IsAlive` | `true` while the thread is running |
| `Thread.CurrentThread` | The thread running this line of code |
| `Thread.Sleep(ms)` | Pauses the current thread |
| `Priority` | A hint to the OS: `Lowest` to `Highest` |

```text
var worker = new Thread(DoWork)
{
    Name = "Report builder",
    IsBackground = true,
    Priority = ThreadPriority.BelowNormal,
};
worker.Start();
Console.WriteLine($"{worker.Name} alive? {worker.IsAlive}");
worker.Join();
Console.WriteLine($"{worker.Name} alive? {worker.IsAlive}");
```

You can read `Thread.CurrentThread` anywhere, including in the browser:

```csharp runnable title="Thread.CurrentThread"
using System;
using System.Threading;

Thread me = Thread.CurrentThread;
me.Name = "Main";

Console.WriteLine($"Name: {me.Name}");
Console.WriteLine($"Alive: {me.IsAlive}");
Console.WriteLine($"Has an id: {me.ManagedThreadId > 0}");
```

Avoid `Thread.Abort()`, `Suspend()`, and `Resume()`. They are unsafe and throw `PlatformNotSupportedException` in modern .NET. To stop a thread, ask it politely with a `CancellationToken` (lesson 3 of Chapter 1).

## Thread Pool

Creating a thread costs time and memory: about 1 MB of stack for each one. A program that creates a new thread for every small job wastes a lot of resources.

The **thread pool** is a group of ready-made threads that .NET keeps alive and **reuses**. You hand it a job, a free pool thread runs it, and then the thread goes back to the pool to wait for the next job.

Think of a taxi rank. Instead of buying a new car for every trip, you take the next free taxi, and it comes back to the rank afterwards.

The pool grows and shrinks automatically. You never create or destroy pool threads yourself.

The low-level way to use the pool is `ThreadPool.QueueUserWorkItem`:

```text
ThreadPool.QueueUserWorkItem(_ => Console.WriteLine("Running on a pool thread"));
```

It has no easy way to get a result back or to know when the job is done. That is why modern code uses `Task.Run` instead.

## Task.Run

**`Task.Run`** queues work on the thread pool and gives you back a `Task` to await. It combines the thread pool with everything you learned about tasks:

- `Task.Run(() => ...)` returns a `Task`.
- `Task.Run(() => value)` returns a `Task<T>` with the result.
- `await` waits for it without blocking.
- Exceptions are stored in the task and rethrown at `await`.

```csharp runnable title="Task.Run with and without a result"
using System;
using System.Threading.Tasks;

await Task.Run(() => Console.WriteLine("Work with no result"));

int primes = await Task.Run(() => CountPrimes(10_000));
Console.WriteLine($"Primes below 10,000: {primes}");

static int CountPrimes(int limit)
{
    int count = 0;
    for (int n = 2; n < limit; n++)
    {
        bool isPrime = true;
        for (int d = 2; d * d <= n; d++)
            if (n % d == 0) { isPrime = false; break; }
        if (isPrime) count++;
    }
    return count;
}
```

In a desktop app, `Task.Run` keeps the window responsive: the slow calculation runs on a pool thread, and the UI thread is free to redraw and handle clicks.

## Thread Pool vs Manual Threads

| | Thread pool (`Task.Run`) | Manual thread (`new Thread`) |
|---|---|---|
| Start-up cost | Very low, threads are reused | Higher, a new thread each time |
| Get a result back | Easy: `Task<T>` and `await` | Hard: shared variables |
| Exceptions | Stored in the task, rethrown at `await` | Crash the whole program if not caught |
| Background or foreground | Always background | Foreground by default |
| Set name, priority | No | Yes |
| Best for | Short and medium jobs: the normal choice | Rare long-running jobs that need special settings |

**Easy Way to Remember:** use `Task.Run` unless you have a specific reason not to.

For work that runs for the whole life of the program, such as a loop that watches a queue, you can tell the pool to use a dedicated thread instead of borrowing one:

```text
Task watcher = Task.Factory.StartNew(WatchQueue, TaskCreationOptions.LongRunning);
```

## CPU-Bound vs I/O-Bound Operations

Slow work comes in two kinds, and they need different tools.

**CPU-bound** work is slow because of **calculation**. The processor is busy the whole time. Examples: resizing an image, compressing a file, counting primes, training a model.

**I/O-bound** work is slow because of **waiting**. The processor is mostly idle. Examples: downloading a file, reading from disk, querying a database, calling an API.

| | CPU-bound | I/O-bound |
|---|---|---|
| Slow because of | Calculation | Waiting for disk, network, or database |
| The CPU during the wait | 100% busy | Mostly idle |
| More cores help? | Yes | No |
| Right tool | `Task.Run`, `Parallel` | `async`/`await` with real async APIs |
| Example | `Task.Run(() => ResizeImage(img))` | `await client.GetStringAsync(url)` |

Putting I/O on `Task.Run` wastes a pool thread that just sits and waits. Using `async` for CPU work does not make it any faster. Match the tool to the kind of work.

```csharp runnable title="CPU work on Task.Run, waiting work with await"
using System;
using System.Threading.Tasks;

// CPU-bound: hand it to a worker thread.
Task<long> calculation = Task.Run(() => Factorial(20));

// I/O-bound: just await it (Task.Delay stands in for a web request).
Task<string> download = DownloadAsync();

Console.WriteLine($"20! = {await calculation}");
Console.WriteLine($"Downloaded: {await download}");

static long Factorial(int n)
{
    long result = 1;
    for (int i = 2; i <= n; i++) result *= i;
    return result;
}

static async Task<string> DownloadAsync()
{
    await Task.Delay(200);
    return "price-list.csv";
}
```

## Managing Multiple Workers

When there is a lot of CPU work, split it into pieces, give each piece to a worker, then combine the results.

A good rule is **one worker per CPU core**. More workers than cores just take turns on the same cores.

```csharp runnable title="Split a big job across one worker per core"
using System;
using System.Linq;
using System.Threading.Tasks;

int[] numbers = Enumerable.Range(1, 100_000).ToArray();
int workers = Math.Max(1, Math.Min(4, Environment.ProcessorCount));
int chunk = (numbers.Length + workers - 1) / workers;

var tasks = new Task<long>[workers];
for (int w = 0; w < workers; w++)
{
    int start = w * chunk;
    int end = Math.Min(start + chunk, numbers.Length);
    tasks[w] = Task.Run(() =>
    {
        long sum = 0;
        for (int i = start; i < end; i++)
            sum += (long)numbers[i] * numbers[i];
        return sum;
    });
}

long[] partSums = await Task.WhenAll(tasks);
Console.WriteLine($"Workers used: {(workers >= 1 ? "at least one" : "none")}");
Console.WriteLine($"Sum of squares 1..100,000 = {partSums.Sum()}");
```

Notice `int start` and `int end` are declared **inside** the loop. Each lambda then keeps its own copy. If the lambda used the loop variable `w` directly, every worker could see its final value and work on the wrong chunk.

Lesson 7 shows `Parallel.For`, which does all of this splitting for you.

## Exception Handling in Threads

Exceptions behave very differently in manual threads and in tasks.

**Manual thread:** an exception that is not caught **inside** the thread crashes the **whole program**. The caller cannot catch it.

```text
var t = new Thread(() => throw new InvalidOperationException("boom"));
try
{
    t.Start();
    t.Join();
}
catch (Exception)       // never reached
{
}
// The unhandled exception on the worker thread ends the whole process.
```

So inside a manual thread, always wrap the body in `try`/`catch`:

```text
var t = new Thread(() =>
{
    try { DoRiskyWork(); }
    catch (Exception ex) { Console.WriteLine($"Worker failed: {ex.Message}"); }
});
```

**Task:** the exception is stored in the task and rethrown at `await`. A normal `try`/`catch` in the caller works, and the program keeps running.

```csharp runnable title="A failing worker inside Task.Run"
using System;
using System.Threading.Tasks;

Task<int> risky = Task.Run(() => ParseAge("twelve"));

try
{
    int age = await risky;
    Console.WriteLine($"Age: {age}");
}
catch (FormatException ex)
{
    Console.WriteLine($"The worker failed: {ex.Message}");
}

Console.WriteLine($"Task status: {risky.Status}");
Console.WriteLine("The program is still running");

static int ParseAge(string text) =>
    int.TryParse(text, out int age) ? age : throw new FormatException($"'{text}' is not a number");
```

When several workers fail, `await Task.WhenAll(...)` throws the first exception, and the `WhenAll` task's `.Exception.InnerExceptions` holds all of them (see Chapter 1, lesson 2).

## Choosing Between Threads, Tasks, and Async Operations

| Situation | Use | Example |
|---|---|---|
| Waiting for disk, network, or database | `async` / `await` | `await File.ReadAllTextAsync(path)` |
| Heavy calculation, keep the UI responsive | `Task.Run` | `await Task.Run(() => Resize(img))` |
| Many calculations over a collection | `Parallel.For` / PLINQ (lesson 7) | `Parallel.ForEach(images, Resize)` |
| A worker that runs for the whole program | `Task.Factory.StartNew` with `LongRunning`, or `new Thread` | A queue watcher |
| Special thread settings (name, priority, STA) | `new Thread` | A COM component that needs STA |

**Easy Way to Remember:**

- **Waiting?** Use `async`/`await`.
- **Calculating?** Use `Task.Run` or `Parallel`.
- **Something unusual?** Only then use `new Thread`.

## Common mistakes

| Mistake | Wrong | Correct | Why |
|---|---|---|---|
| Forgetting `Start()` | `new Thread(Work);` | `new Thread(Work).Start();` | A thread does nothing until it is started |
| Not waiting for a thread | `t.Start();` then use its result | `t.Start(); t.Join();` | The result is not ready yet |
| Starting a thread twice | `t.Start(); t.Start();` | Create a new `Thread` | A stopped thread cannot restart |
| Uncaught error in a thread | `new Thread(() => Risky())` | Wrap the body in `try`/`catch` | It crashes the whole program |
| New thread per small job | `new Thread(...)` in a loop | `Task.Run(...)` | Threads are costly; the pool reuses them |
| `Task.Run` around async I/O | `Task.Run(() => DownloadAsync())` | `await DownloadAsync()` | Wastes a pool thread that just waits |
| Lambda captures the loop variable | `Task.Run(() => Use(i))` | `int copy = i; Task.Run(() => Use(copy))` | Workers may see the wrong value |
| Background work cut off | `IsBackground = true` for a file save | Keep it foreground, or await it | Background threads stop when the app exits |

## What to Remember

- A **thread** is a worker that runs code. A **process** is a running program, and it can contain many threads that share memory.
- `new Thread(method)` creates a thread, `Start()` runs it, `Join()` waits for it. A thread can be started only once.
- A thread moves from `Unstarted` to `Running`, maybe `WaitSleepJoin`, and finally `Stopped`.
- Foreground threads keep the program alive; background threads (including all pool threads) do not.
- The **thread pool** reuses threads. `Task.Run` puts work on the pool and gives you a task to await.
- Prefer `Task.Run` over `new Thread` unless you need special settings or a long-running worker.
- **CPU-bound** work needs threads (`Task.Run`, `Parallel`); **I/O-bound** work needs `async`/`await`.
- Split big CPU jobs into about one piece per core, then combine the results.
- An uncaught exception in a manual thread crashes the program; in a task it is rethrown at `await`.

Next: Synchronization
