Introduction

Multithreading and parallel programming are essential concepts in modern software development, allowing programs to perform multiple operations concurrently, thus improving performance and responsiveness. This section will cover the basics of multithreading, thread management, synchronization, and parallel programming techniques in C#.

Key Concepts

  1. Thread: The smallest unit of processing that can be scheduled by an operating system.
  2. Multithreading: The ability of a CPU to execute multiple threads concurrently.
  3. Parallel Programming: A type of computation in which many calculations or processes are carried out simultaneously.
  4. Task Parallel Library (TPL): A set of public types and APIs in the System.Threading and System.Threading.Tasks namespaces that simplify the process of adding parallelism and concurrency to applications.

Creating and Managing Threads

Creating a Thread

In C#, you can create a thread using the Thread class from the System.Threading namespace.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(new ThreadStart(DoWork));
        thread.Start();
    }

    static void DoWork()
    {
        Console.WriteLine("Thread is working...");
    }
}

Explanation

  • ThreadStart Delegate: Represents the method that executes on the thread.
  • Start Method: Begins the execution of the thread.

Practical Exercise

Exercise: Create a program that starts a new thread to print numbers from 1 to 10.

Solution:

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Thread thread = new Thread(new ThreadStart(PrintNumbers));
        thread.Start();
    }

    static void PrintNumbers()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine(i);
            Thread.Sleep(500); // Sleep for 500 milliseconds
        }
    }
}

Common Mistakes

  • Not Joining Threads: Forgetting to join threads can lead to unexpected behavior if the main thread terminates before the worker thread completes.
  • Improper Synchronization: Accessing shared resources without proper synchronization can lead to race conditions.

Synchronization

Lock Statement

The lock statement is used to ensure that a block of code runs to completion without interruption by other threads.

class Counter
{
    private int count = 0;
    private readonly object lockObject = new object();

    public void Increment()
    {
        lock (lockObject)
        {
            count++;
        }
    }

    public int GetCount()
    {
        lock (lockObject)
        {
            return count;
        }
    }
}

Practical Exercise

Exercise: Modify the previous exercise to use a shared counter that is incremented by two threads.

Solution:

using System;
using System.Threading;

class Program
{
    static Counter counter = new Counter();

    static void Main()
    {
        Thread thread1 = new Thread(new ThreadStart(IncrementCounter));
        Thread thread2 = new Thread(new ThreadStart(IncrementCounter));

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();

        Console.WriteLine($"Final Counter Value: {counter.GetCount()}");
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 10; i++)
        {
            counter.Increment();
            Thread.Sleep(100);
        }
    }
}

class Counter
{
    private int count = 0;
    private readonly object lockObject = new object();

    public void Increment()
    {
        lock (lockObject)
        {
            count++;
        }
    }

    public int GetCount()
    {
        lock (lockObject)
        {
            return count;
        }
    }
}

Task Parallel Library (TPL)

Using Tasks

Tasks are a higher-level abstraction over threads, providing a more flexible and easier way to work with asynchronous and parallel code.

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Task task = Task.Run(() => DoWork());
        task.Wait();
    }

    static void DoWork()
    {
        Console.WriteLine("Task is working...");
    }
}

Parallel.For and Parallel.ForEach

The Parallel class provides methods for parallel loops.

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.For(0, 10, i =>
        {
            Console.WriteLine($"Processing {i}");
        });

        string[] words = { "apple", "banana", "cherry" };
        Parallel.ForEach(words, word =>
        {
            Console.WriteLine($"Processing {word}");
        });
    }
}

Practical Exercise

Exercise: Use Parallel.For to print numbers from 1 to 10.

Solution:

using System;
using System.Threading.Tasks;

class Program
{
    static void Main()
    {
        Parallel.For(1, 11, i =>
        {
            Console.WriteLine(i);
        });
    }
}

Conclusion

In this section, we covered the basics of multithreading and parallel programming in C#. We learned how to create and manage threads, synchronize access to shared resources, and use the Task Parallel Library (TPL) for more efficient parallel processing. Understanding these concepts is crucial for developing high-performance and responsive applications. In the next module, we will delve into working with data, including file I/O, serialization, and database connectivity.

© Copyright 2024. All rights reserved