ScholarQuill logoScholarQuillUniversity Notes
My Semester
Explore Notes
Past Papers12 New
Video CoursesNew
Search
Settings
Study Tools
GPA/CGPA Calculator
Schedule & To-Do
Student Portal
Contact
Contact
ScholarQuill logo
My Semester
Explore Notes
Past Papers12 New
Video CoursesNew
Search
Settings
GPA/CGPA Calculator
Schedule & To-Do
Student Portal
Contact
ScholarQuill logoScholarQuillUniversity Notes
Study PortalCSI-415Threading And Synchron…
Ctrl + K
Login
Explore
Search
Semester
Papers
CGPA
    CSI-415

    Advanced Programming

    Progress0 / 55 done
    Topics55
    1Visual Programming Basics2Introduction to Events3Fundamentals of Event-Driven Programming4Message Handling5User Interfaces6Graphics Device Interface7Painting and Drawing8Windows Management9Input Devices10Resources11String and Menu Resource12Dialogs and Windows Controls13Common Controls14Dynamic Link Libraries (DLLs)15Threads and Synchronization16Network Programming17Building Class Libraries at the Command Line18Class Libraries19Using References20Assemblies21Private Assembly Deployment22Shared Assembly Deployment23Configuration Overview24Configuration Files25Programmatic Access to Configuration26Using SDK Tools for Signing and Deployment27Metadata28Reflection29Late Binding30Directories and Files31Serialization32Attributes33Memory Management and Garbage Collection34Threading and Synchronization35Asynchronous Delegates36Application Domains37Marshal by Value38Marshal by Reference39Authentication and Authorization40Configuring Security41Code Access Security42Code Groups43Evidence44Permissions45Role-Based Security46Principals and Identities47Using Data Readers48Using Data Sets49Interacting with XML Data50Tracing Event Logs51Using the Boolean Switch and Trace Switch Classes52Print Debugging Information with the Debug Class53Instrumenting Release Builds with the Trace Class54Using Listeners55Implementing Custom Listeners
    8 min read
    Topic 34 of 55
    Topic Study Note Illustration
    34

    Threading and Synchronization

    In C#, threading and synchronization are essential concepts for building applications that can perform multiple tasks concurrently. Threading allows programs to execute multiple operations simultaneously, while synchronization...

    Threading and Synchronization in C#

    In C#, threading and synchronization are essential concepts for building applications that can perform multiple tasks concurrently. Threading allows programs to execute multiple operations simultaneously, while synchronization ensures that shared resources are accessed in a thread-safe manner to avoid data corruption or inconsistent states.

    1. Threading in C#

    A thread is the smallest unit of execution in a program. A single process can have multiple threads running concurrently, allowing multiple operations to be performed at the same time. C# provides the System.Threading namespace, which includes classes and methods to create and manage threads.

    A. Creating and Starting Threads

    In C#, you can create threads using the Thread class. Threads can be started by calling the Start() method, and the code to be executed on the thread is defined in a method or delegate.

    using System;
    using System.Threading;
    
    public class Program
    {
        // Method to be executed on a separate thread
        static void PrintNumbers()
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(1000); // Sleep for 1 second between prints
            }
        }
    
        public static void Main()
        {
            // Creating a thread and passing the method to execute
            Thread t = new Thread(PrintNumbers);
            t.Start(); // Start the thread
    
            Console.WriteLine("Main thread is free to do other work.");
            t.Join(); // Wait for the thread to complete
        }
    }
    

    In this example:

    • A new thread is created to run the PrintNumbers method.
    • Thread.Start() starts the thread.
    • Thread.Join() waits for the thread to complete before proceeding with the main thread.

    B. Thread States

    A thread can be in one of several states during its lifecycle:

    • Unstarted: The thread is created but has not yet been started.
    • Running: The thread is actively executing code.
    • Waiting: The thread is waiting for an event (e.g., sleep, I/O operation).
    • Blocked: The thread is blocked from running, typically due to synchronization mechanisms.
    • Dead: The thread has finished executing and is no longer running.

    C. Thread Pool

    The thread pool is a collection of worker threads that can be used for executing tasks asynchronously without manually creating new threads. Using the thread pool is more efficient because it reuses existing threads, minimizing the overhead of thread creation and destruction.

    using System;
    using System.Threading;
    
    public class Program
    {
        static void PrintNumbers(object state)
        {
            for (int i = 1; i <= 5; i++)
            {
                Console.WriteLine(i);
                Thread.Sleep(1000);
            }
        }
    
        public static void Main()
        {
            // Queue a work item to the thread pool
            ThreadPool.QueueUserWorkItem(PrintNumbers);
            
            Console.WriteLine("Main thread is free to do other work.");
            Thread.Sleep(6000); // Give enough time for the thread pool task to complete
        }
    }
    

    In this example, ThreadPool.QueueUserWorkItem is used to queue a task for execution on the thread pool.


    2. Synchronization in C#

    Synchronization is critical when multiple threads access shared resources to ensure that only one thread can access a resource at a time, preventing data corruption. Without proper synchronization, you can encounter race conditions, where the result depends on the order of thread execution.

    A. Locks (Monitor)

    One of the primary ways to synchronize threads in C# is by using the lock keyword or the Monitor class. The lock keyword is shorthand for acquiring a lock on an object to ensure that only one thread can execute a block of code at a time.

    using System;
    using System.Threading;
    
    public class Program
    {
        static object lockObject = new object();
        static int counter = 0;
    
        static void IncrementCounter()
        {
            lock (lockObject) // Locking the critical section
            {
                for (int i = 0; i < 1000; i++)
                {
                    counter++; // Critical section
                }
            }
        }
    
        public static void Main()
        {
            Thread t1 = new Thread(IncrementCounter);
            Thread t2 = new Thread(IncrementCounter);
    
            t1.Start();
            t2.Start();
    
            t1.Join();
            t2.Join();
    
            Console.WriteLine("Counter value: " + counter); // Expected: 2000
        }
    }
    

    In this example:

    • The lock keyword ensures that only one thread can enter the IncrementCounter method at a time, preventing a race condition.
    • lockObject is used as the synchronization object, and only one thread can acquire the lock on this object at a time.

    B. Monitor Class

    The Monitor class provides more control over locking. It allows you to explicitly acquire and release locks on an object, and it provides advanced features like timeouts and waiting for conditions.

    using System;
    using System.Threading;
    
    public class Program
    {
        static object lockObject = new object();
    
        static void ThreadSafeMethod()
        {
            Monitor.Enter(lockObject);
            try
            {
                // Critical section
                Console.WriteLine("Thread is executing.");
            }
            finally
            {
                Monitor.Exit(lockObject);
            }
        }
    
        public static void Main()
        {
            Thread t1 = new Thread(ThreadSafeMethod);
            Thread t2 = new Thread(ThreadSafeMethod);
    
            t1.Start();
            t2.Start();
    
            t1.Join();
            t2.Join();
        }
    }
    

    In this example, Monitor.Enter is used to acquire a lock, and Monitor.Exit is used to release it.

    C. Mutexes

    A mutex is a synchronization primitive that can be used to manage access to resources across multiple processes, not just threads. It ensures that only one thread or process can enter a critical section at any given time.

    using System;
    using System.Threading;
    
    public class Program
    {
        static Mutex mutex = new Mutex();
    
        static void ThreadSafeMethod()
        {
            mutex.WaitOne(); // Acquire the mutex
            try
            {
                // Critical section
                Console.WriteLine("Thread is executing.");
            }
            finally
            {
                mutex.ReleaseMutex(); // Release the mutex
            }
        }
    
        public static void Main()
        {
            Thread t1 = new Thread(ThreadSafeMethod);
            Thread t2 = new Thread(ThreadSafeMethod);
    
            t1.Start();
            t2.Start();
    
            t1.Join();
            t2.Join();
        }
    }
    

    In this example, mutex.WaitOne() is used to acquire the mutex, and mutex.ReleaseMutex() is used to release it.

    D. Semaphores

    A semaphore is used to control access to a resource pool. It allows multiple threads to access a resource concurrently, but only up to a specified limit.

    using System;
    using System.Threading;
    
    public class Program
    {
        static Semaphore semaphore = new Semaphore(2, 3); // Max 3 threads can access simultaneously
    
        static void ThreadSafeMethod()
        {
            semaphore.WaitOne(); // Wait until semaphore is available
            try
            {
                // Simulate work
                Console.WriteLine("Thread is executing.");
                Thread.Sleep(1000); // Simulate work
            }
            finally
            {
                semaphore.Release(); // Release semaphore
            }
        }
    
        public static void Main()
        {
            Thread t1 = new Thread(ThreadSafeMethod);
            Thread t2 = new Thread(ThreadSafeMethod);
            Thread t3 = new Thread(ThreadSafeMethod);
            Thread t4 = new Thread(ThreadSafeMethod);
    
            t1.Start();
            t2.Start();
            t3.Start();
            t4.Start();
    
            t1.Join();
            t2.Join();
            t3.Join();
            t4.Join();
        }
    }
    

    In this example:

    • The semaphore allows up to 3 threads to access the critical section at the same time.
    • semaphore.WaitOne() waits until the semaphore is available, and semaphore.Release() releases the semaphore.

    E. Other Synchronization Primitives

    • AutoResetEvent and ManualResetEvent: These are used for signaling between threads. An AutoResetEvent automatically resets after being signaled, whereas a ManualResetEvent remains signaled until explicitly reset.
    • ReaderWriterLockSlim: This allows multiple threads to read shared data concurrently but ensures exclusive access when data is written.

    3. Thread Safety Best Practices

    • Minimize Shared State: Keep the number of shared resources to a minimum to reduce the need for synchronization.
    • Prefer Immutable Objects: If objects are immutable, they don't need to be locked or synchronized because their state cannot change once they are created.
    • Use Concurrent Collections: The .NET framework provides thread-safe collections like ConcurrentQueue, ConcurrentDictionary, etc., which are optimized for concurrent access.

    4. Summary

    • Threading in C# enables concurrent execution of tasks and is managed using the Thread class and thread pools.
    • Synchronization ensures thread safety when multiple threads access shared resources. Common synchronization mechanisms include lock, Monitor, Mutex, Semaphore, and event objects.
    • Thread safety can be achieved by carefully managing access to shared resources, using appropriate synchronization primitives, and designing systems with minimal shared state.

    By understanding and applying these concepts, you can create efficient and reliable multithreaded applications

    ← Previous topic 33
    Memory Management and Garbage Collection
    Next topic 35 →
    Asynchronous Delegates

    Past Papers

    Open this section to view and download past papers

    Click on Show Past Papers to load available exam papers.

    Reading Progress
    0%
    8mremaining
    1.3kwords
    Intermediatelevel
    On This Page

    No headings found

    Study Toolkit

    Subject Portal

    Advanced Programming