C# System.Threading

Multithreading and asynchronous programming

โšก What is System.Threading?

System.Threading enables concurrent programming by running multiple operations simultaneously. It helps create responsive applications that can perform background tasks, handle multiple users, and utilize modern multi-core processors efficiently.


// Simple thread example
using System.Threading;

Thread thread = new Thread(() => Console.WriteLine("Hello from thread!"));
thread.Start();
                                    

Core Threading Concepts

๐Ÿงต

Thread

Create and manage threads

Thread t = new Thread(Method);
t.Start();
โฑ๏ธ

Task

Modern async operations

Task task = Task.Run(() => Work());
await task;
๐Ÿ”’

Lock

Synchronize access

lock (lockObject) {
    // Thread-safe code
}
โธ๏ธ

Sleep

Pause thread execution

Thread.Sleep(1000);
await Task.Delay(1000);

๐Ÿ”น Creating and Starting Threads

Threads in C# enable concurrent execution, allowing multiple operations to run simultaneously. By instantiating the Thread class and providing a method delegate, you can start parallel tasks, improving performance for CPU-intensive or blocking operations. Threads are managed by the OS and can run independently, making them suitable for background processing, real-time updates, and responsive UI. However, they require careful synchronization to avoid issues like race conditions or deadlocks.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("Main thread started");
        
        // Create thread with method
        Thread thread1 = new Thread(PrintNumbers);
        thread1.Start();
        
        // Create thread with lambda
        Thread thread2 = new Thread(() =>
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine($"Thread 2: {i}");
                Thread.Sleep(300);
            }
        });
        thread2.Start();
        
        // Main thread continues
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Main: {i}");
            Thread.Sleep(200);
        }
        
        // Wait for threads to complete
        thread1.Join();
        thread2.Join();
        
        Console.WriteLine("All threads completed");
    }
    
    static void PrintNumbers()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Thread 1: {i}");
            Thread.Sleep(400);
        }
    }
}

Output:

Main thread started

Main: 0

Thread 2: 0

Thread 1: 0

Main: 1

Thread 2: 1

Main: 2

Thread 1: 1

All threads completed

๐Ÿ”น Task-Based Async Programming

Task-based asynchronous programming in C# simplifies parallel execution using the Task Parallel Library (TPL). Tasks represent asynchronous operations and are more efficient than raw threads, as they utilize the thread pool optimally. Methods like Task.Run() and Task.WhenAll() help execute and coordinate multiple tasks. This model enhances scalability, especially in I/O-bound applications like web requests or file operations, by reducing resource overhead and improving responsiveness.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Starting tasks...");
        
        // Create and start task
        Task task1 = Task.Run(() => DoWork("Task 1", 1000));
        
        // Create task with return value
        Task<int> task2 = Task.Run(() =>
        {
            Console.WriteLine("Task 2 calculating...");
            Task.Delay(500).Wait();
            return 42;
        });
        
        // Async method
        Task task3 = DoWorkAsync("Task 3", 800);
        
        // Wait for all tasks
        await Task.WhenAll(task1, task2, task3);
        
        // Get result from task2
        int result = await task2;
        Console.WriteLine($"Task 2 result: {result}");
        
        Console.WriteLine("All tasks completed");
    }
    
    static void DoWork(string name, int delay)
    {
        Console.WriteLine($"{name} started");
        Task.Delay(delay).Wait();
        Console.WriteLine($"{name} completed");
    }
    
    static async Task DoWorkAsync(string name, int delay)
    {
        Console.WriteLine($"{name} started");
        await Task.Delay(delay);
        Console.WriteLine($"{name} completed");
    }
}

Output:

Starting tasks...

Task 1 started

Task 2 calculating...

Task 3 started

Task 3 completed

Task 1 completed

Task 2 result: 42

All tasks completed

๐Ÿ”น Async and Await Keywords

The async and await keywords in C# enable clean, readable asynchronous programming. By marking a method with async, you allow it to use await to pause execution without blocking the thread, perfect for I/O operations like API calls. For example, await client.GetStringAsync(url) frees the thread while waiting. This improves app responsiveness and scalability, especially in web and mobile applications where waiting for network data is common.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Starting async operations...");
        
        // Call async method
        string result1 = await FetchDataAsync("API 1");
        Console.WriteLine($"Result: {result1}");
        
        // Run multiple async operations in parallel
        Task<string> task1 = FetchDataAsync("API 2");
        Task<string> task2 = FetchDataAsync("API 3");
        
        string[] results = await Task.WhenAll(task1, task2);
        
        Console.WriteLine("All results:");
        foreach (string result in results)
        {
            Console.WriteLine($"- {result}");
        }
    }
    
    static async Task<string> FetchDataAsync(string source)
    {
        Console.WriteLine($"Fetching from {source}...");
        
        // Simulate async operation
        await Task.Delay(1000);
        
        Console.WriteLine($"{source} completed");
        return $"Data from {source}";
    }
}

Output:

Starting async operations...

Fetching from API 1...

API 1 completed

Result: Data from API 1

Fetching from API 2...

Fetching from API 3...

API 2 completed

API 3 completed

All results:

- Data from API 2

- Data from API 3

๐Ÿ”น Thread Synchronization with Lock

The lock statement ensures thread-safe access to shared resources in multithreaded applications. It prevents race conditions by allowing only one thread at a time to execute a critical section of code. For instance, lock (lockObject) { counter++; } guarantees accurate increments. Without synchronization, concurrent writes can corrupt data, leading to unpredictable results. Always lock on a private object to avoid deadlocks and maintain application integrity.

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static int counter = 0;
    static object lockObject = new object();
    
    static void Main()
    {
        // Without lock - may produce incorrect results
        counter = 0;
        Task[] tasks1 = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks1[i] = Task.Run(() => IncrementWithoutLock());
        }
        Task.WaitAll(tasks1);
        Console.WriteLine($"Without lock: {counter}");
        
        // With lock - always correct
        counter = 0;
        Task[] tasks2 = new Task[5];
        for (int i = 0; i < 5; i++)
        {
            tasks2[i] = Task.Run(() => IncrementWithLock());
        }
        Task.WaitAll(tasks2);
        Console.WriteLine($"With lock: {counter}");
    }
    
    static void IncrementWithoutLock()
    {
        for (int i = 0; i < 1000; i++)
        {
            counter++;
        }
    }
    
    static void IncrementWithLock()
    {
        for (int i = 0; i < 1000; i++)
        {
            lock (lockObject)
            {
                counter++;
            }
        }
    }
}

Output:

Without lock: 4823

With lock: 5000

๐Ÿ”น Thread Sleep and Delay

Thread.Sleep and Task.Delay both introduce pauses but behave differently. Thread.Sleep blocks the current thread, making it unsuitable for async contexts. In contrast, Task.Delay returns a task that completes after a specified time, freeing the thread for other work. Use Task.Delay in asynchronous methods for non-blocking waits, such as polling or implementing timeouts, ensuring efficient resource utilization.

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        Console.WriteLine("Demonstrating delays...");
        
        // Thread.Sleep (blocks thread)
        Console.WriteLine("Starting Thread.Sleep...");
        Thread.Sleep(1000);
        Console.WriteLine("Thread.Sleep completed (1 second)");
        
        // Task.Delay (async, doesn't block)
        Console.WriteLine("\nStarting Task.Delay...");
        await Task.Delay(1000);
        Console.WriteLine("Task.Delay completed (1 second)");
        
        // Countdown example
        Console.WriteLine("\nCountdown:");
        for (int i = 5; i > 0; i--)
        {
            Console.WriteLine(i);
            await Task.Delay(500);
        }
        Console.WriteLine("Go!");
        
        // Periodic task
        Console.WriteLine("\nPeriodic updates:");
        for (int i = 0; i < 3; i++)
        {
            Console.WriteLine($"Update {i + 1}");
            await Task.Delay(800);
        }
    }
}

Output:

Demonstrating delays...

Starting Thread.Sleep...

Thread.Sleep completed (1 second)

Starting Task.Delay...

Task.Delay completed (1 second)

Countdown:

5

4

3

2

1

Go!

๐Ÿ”น CancellationToken

A CancellationToken enables cooperative cancellation of long-running or asynchronous operations. By passing the token to methods and periodically checking cancellationToken.IsCancellationRequested, you can gracefully stop tasks when needed. This prevents resource leaks and improves user experience by allowing responsive interruptions, such as canceling a file download or stopping a background data fetch without forcing the application to hang or crash.

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // Create cancellation token source
        CancellationTokenSource cts = new CancellationTokenSource();
        
        // Start long-running task
        Task task = LongRunningOperationAsync(cts.Token);
        
        // Cancel after 2 seconds
        Console.WriteLine("Task started. Will cancel in 2 seconds...");
        await Task.Delay(2000);
        
        cts.Cancel();
        Console.WriteLine("Cancellation requested");
        
        try
        {
            await task;
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("Task was cancelled");
        }
        
        Console.WriteLine("Program completed");
    }
    
    static async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            // Check if cancellation requested
            token.ThrowIfCancellationRequested();
            
            Console.WriteLine($"Working... {i + 1}/10");
            await Task.Delay(500, token);
        }
        
        Console.WriteLine("Operation completed");
    }
}

Output:

Task started. Will cancel in 2 seconds...

Working... 1/10

Working... 2/10

Working... 3/10

Working... 4/10

Cancellation requested

Task was cancelled

Program completed

๐Ÿง  Test Your Knowledge

Which keyword is used to wait for an async operation without blocking?