ScholarQuill logoScholarQuillUniversity Notes
  • Notes
  • Past Papers
  • Blogs
  • Todo
Login
ScholarQuill logoScholarQuillUniversity Notes
Login
NotesPast PapersBlogsTodo
More
SubjectsDiscussionCGPA CalculatorGPA CalculatorStudent PortalCourse Outline
About
About usPrivacy PolicyReportContact
Notes
Past Papers
Blogs
Todo
Analytics
    Current Subject
    🧩
    Advanced Programming
    CSI-415
    Progress0 / 55 topics
    Topics
    1. Visual Programming Basics2. Introduction to Events3. Fundamentals of Event-Driven Programming4. Message Handling5. User Interfaces6. Graphics Device Interface7. Painting and Drawing8. Windows Management9. Input Devices10. Resources11. String and Menu Resource12. Dialogs and Windows Controls13. Common Controls14. Dynamic Link Libraries (DLLs)15. Threads and Synchronization16. Network Programming17. Building Class Libraries at the Command Line18. Class Libraries19. Using References20. Assemblies21. Private Assembly Deployment22. Shared Assembly Deployment23. Configuration Overview24. Configuration Files25. Programmatic Access to Configuration26. Using SDK Tools for Signing and Deployment27. Metadata28. Reflection29. Late Binding30. Directories and Files31. Serialization32. Attributes33. Memory Management and Garbage Collection34. Threading and Synchronization35. Asynchronous Delegates36. Application Domains37. Marshal by Value38. Marshal by Reference39. Authentication and Authorization40. Configuring Security41. Code Access Security42. Code Groups43. Evidence44. Permissions45. Role-Based Security46. Principals and Identities47. Using Data Readers48. Using Data Sets49. Interacting with XML Data50. Tracing Event Logs51. Using the Boolean Switch and Trace Switch Classes52. Print Debugging Information with the Debug Class53. Instrumenting Release Builds with the Trace Class54. Using Listeners55. Implementing Custom Listeners
    CSI-415›Threading and Synchronization
    Advanced ProgrammingTopic 34 of 55

    Threading and Synchronization

    8 minread
    1,293words
    Intermediatelevel

    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 load past papers

    Click on Show Past Papers to see past papers.
    On This Page
      Reading Stats
      Est. reading time8 min
      Word count1,293
      Code examples0
      DifficultyIntermediate