Fundamentals 12 min read

C# Multithreading Basics: Introduction, Join, Sleep, and Thread Safety

This article introduces C# multithreading concepts, covering thread creation, memory isolation, data sharing, thread safety with locks, and the use of Join and Sleep methods, while providing clear code examples and explanations of how threads are scheduled and interact with the CPU.

Wukong Talks Architecture
Wukong Talks Architecture
Wukong Talks Architecture
C# Multithreading Basics: Introduction, Join, Sleep, and Thread Safety

C# Multithreading Basics

C# supports parallel execution through multithreading. The main thread is created automatically by the CLR and the operating system, and additional threads can be created to achieve concurrency.

1. Thread Creation Example

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(WriteY); // create a thread
        thread.Start(); // start the thread
        for (int i = 0; i < 1000; i++) // main thread loop
        {
            Console.Write("x");
        }
        Console.ReadLine();
    }
    static void WriteY()
    {
        for (int i = 0; i < 1000; i++)
        {
            Console.Write("y");
        }
    }
}

The IsAlive property of a thread returns true until the thread finishes; a thread cannot be restarted after it ends.

2. Memory Isolation

Each thread receives its own stack, so local variables are isolated. The following example shows two threads calling the same method with separate stack copies, producing ten "y" characters.

class Program
{
    static void Main(string[] args)
    {
        new Thread(Go).Start();
        Go();
        Console.ReadKey();
    }
    static void Go()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.Write("y");
        }
    }
}

3. Data Sharing

When multiple threads hold a reference to the same object, they share that object's data. The example below demonstrates a shared boolean field done that is printed only once because both threads access the same instance.

class Program
{
    bool done = false;
    static void Main(string[] args)
    {
        Program p = new Program();
        new Thread(p.Go).Start();
        p.Go();
        Console.ReadKey();
    }
    void Go()
    {
        if (!done)
        {
            done = true;
            Console.WriteLine("Done");
        }
    }
}

Static fields provide another way to share data across threads:

class ThreadTest
{
    static bool done; // shared by all threads
    static void Main()
    {
        new Thread(Go).Start();
        Go();
    }
    static void Go()
    {
        if (!done) { done = true; Console.WriteLine("Done"); }
    }
}

4. Thread Safety

Without proper synchronization, the shared done flag may be printed twice. Using the lock keyword ensures that only one thread can enter the critical section at a time.

class Program
{
    static bool done = false;
    static readonly object locker = new object();
    static void Main(string[] args)
    {
        new Thread(Go).Start();
        Go();
        Console.ReadKey();
    }
    static void Go()
    {
        lock (locker)
        {
            if (!done)
            {
                Console.WriteLine("Done");
                done = true;
            }
        }
    }
}

5. Join

The Join method blocks the calling thread until the specified thread terminates.

static void Main(string[] args)
{
    Thread t = new Thread(Go);
    t.Start();
    t.Join();
    Console.WriteLine("Thread t has ended!");
    Console.ReadKey();
}
static void Go()
{
    for (int i = 0; i < 1000; i++)
    {
        Console.Write("y");
    }
}

6. Sleep

Thread.Sleep pauses the current thread for a specified duration without consuming CPU resources. Examples:

Thread.Sleep(TimeSpan.FromHours(1)); // sleep one hour
Thread.Sleep(500); // sleep 500 milliseconds
Thread.Sleep(0); // yield the time slice to other threads

In .NET 4.0, Thread.Yield() performs a similar operation but only yields when the thread is in the same process.

7. How Threads Work

A thread scheduler assigns CPU time slices to active threads. On a single‑core machine, time slicing switches between threads, typically every 10 µs. On multi‑core machines, threads can run truly concurrently on different cores, though the OS still performs scheduling.

Threads may be pre‑empted by the scheduler; they cannot control when they are interrupted.

8. Threads vs. Processes

Threads run within a single process and share the heap, while processes are isolated from each other. This shared memory makes threads useful for background tasks such as fetching data while the UI remains responsive.

9. Uses and Misuses of Multithreading

Common uses include keeping the UI responsive, maximizing CPU utilization, parallel programming, speculative execution, and handling multiple simultaneous requests in servers (e.g., ASP.NET, WCF). Misuse can increase complexity, cause race conditions, and lead to hard‑to‑reproduce bugs if shared data is not properly synchronized.

A good practice is to encapsulate threading logic in reusable, testable classes and to use the .NET framework’s higher‑level concurrency constructs when possible.

C++threadThread SafetyJoinsleep
Wukong Talks Architecture
Written by

Wukong Talks Architecture

Explaining distributed systems and architecture through stories. Author of the "JVM Performance Tuning in Practice" column, open-source author of "Spring Cloud in Practice PassJava", and independently developed a PMP practice quiz mini-program.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.