C#
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C#
-
Section 4: Working with Data
-
Section 5: Error Handling
-
Section 6: Delegates and Events
-
Section 7: Async Programming
-
Section 8: More Language Features
-
Section 9: File I/O and Serialization
-
Section 10: Networking in .NET
-
Section 11: The .NET Ecosystem
-
Section 12: Memory and Performance
-
Section 13: Concurrency Beyond Async
-
Section 14: Reflection and Attributes
-
Section 15: Testing and Best Practices
-
Section 16: Design Patterns in C#
-
Section 17: Standard Library Deep Dive
-
Section 18: Data Structures and Algorithms in C#
-
Section 19: GUI and Desktop Development Overview
-
Section 20: Practical Projects
-
Section 21: More Practice Exercises
-
Section 22: More Standard Library and Text Processing
-
Section 23: More Design Patterns
-
Section 24: More Projects
-
Section 25: Interview Practice
-
Section 26: C# Keywords Reference (Modifiers)
-
Section 27: C# Keywords Reference (Statements)
-
Section 28: C# Keywords Reference (Operators)
-
Section 29: BCL: System.Collections.Generic
-
Section 30: BCL: System.Linq
-
Section 31: BCL: System.Threading
-
Section 32: BCL: System.IO
-
Section 33: BCL: System.Text and System.Text.Json
-
Section 34: BCL: System.Net.Http
-
Section 35: C# Language Specification Topics
-
Section 36: More Practice Exercises
-
Section 37: More Async Patterns
-
Section 38: More BCL: System.Reflection and System.Diagnostics
-
Section 39: Nullable Reference Types In Depth
-
Section 40: C# Records and Pattern Matching In Depth
-
Section 41: Dependency Injection Deep Dive
-
Section 42: More Interview and Whiteboard Practice
197: ManualResetEvent and AutoResetEvent
I've spent a lot of time reviewing junior code, and there is one mistake that pops up almost every time we hit synchronization primitives: the belief that ManualResetEvent and AutoResetEvent are just alternative ways to write a lock statement.
Thinking Events Protect Data Like Locks Do
If you think of these events as "locks," you're going to run into race conditions that are a nightmare to debug. A lock (or a Mutex) is about mutual exclusionβit ensures that only one thread can touch a piece of data at a time. Events, however, are about signaling. They aren't there to stop two threads from crashing into each other; they are there to tell a thread, "Okay, the thing you were waiting for is finally ready."
Imagine you have a ManualResetEvent and you use it to "lock" a shared list. If you call Set() to let a thread in, but forget to call Reset(), every single other thread in your application will just barge right through that gate. You haven't protected your data; you've just built a door that you forgot to close. To protect data, use lock. To coordinate timing, use events.
ManualResetEvent: The Broadcast Signal
Think of a ManualResetEvent as a security gate at a concert. Once the promoter opens the gate (calls Set()), it stays open. Every single person in line (every thread calling WaitOne()) can walk through. They don't have to wait for the gate to be opened again; they just flow through until someone manually shuts the gate (calls Reset()).
I usually use these for "global" states. For example, if you have five different background services that all need to wait until the main configuration file is loaded before they start working, a ManualResetEvent is your best bet. One thread loads the config and calls Set(), and all five services wake up simultaneously.
// The gate starts closed (false)
ManualResetEvent configLoadedEvent = new ManualResetEvent(false);
// Background thread
Task.Run(() => {
Console.WriteLine("Service A waiting for config...");
configLoadedEvent.WaitOne();
Console.WriteLine("Service A starting!");
});
// Main thread
Thread.Sleep(2000); // Simulating loading time
Console.WriteLine("Config loaded. Opening the gate!");
configLoadedEvent.Set(); // Everyone waiting is now released
AutoResetEvent: The Turnstile
An AutoResetEvent is completely different. It's not a gate; it's a turnstile. Only one person can go through at a time. As soon as one thread calls WaitOne() and is allowed to pass, the turnstile automatically snaps shut behind them. If another thread is waiting, it has to wait for the signal to be Set() again.
This is incredibly useful for producer-consumer scenarios. If you have a thread that processes a single item from a queue, you don't want every worker thread to wake up when only one item is available. That would cause a "thundering herd" problem where ten threads fight over one piece of data. With AutoResetEvent, only one thread wakes up, takes the item, and the event automatically resets for the next one.
// The turnstile starts closed
AutoResetEvent itemReadyEvent = new AutoResetEvent(false);
// Worker thread
Task.Run(() => {
while(true) {
itemReadyEvent.WaitOne(); // Wait for signal
Console.WriteLine("Worker processed one item.");
// No need to call Reset(); it happened automatically!
}
});
// Producer thread
itemReadyEvent.Set(); // Releases ONE thread
Thread.Sleep(1000);
itemReadyEvent.Set(); // Releases the next ONE thread
My rule of thumb? If you're saying "Everyone wake up!", use ManualResetEvent. If you're saying "One of you wake up and handle this," use AutoResetEvent.
π Practical Task
Exercise: The Multi-Stage System Bootloader
You are building a simulation of a system boot-up sequence. The system has three components: Hardware, Network, and UI.
- The Hardware and Network modules must both initialize before the UI can start. (Hint: Use a
ManualResetEventto signal the UI that the system is "Ready"). - Once the UI is running, it will send "Initialization Tasks" (like loading user preferences, checking updates, and syncing cache) to a single Background Worker thread. These tasks must be processed one by one. (Hint: Use an
AutoResetEventto signal the worker that a new task is available).
Requirements:
- Create a
ManualResetEventnamed_systemReady. Start the UI thread, but make it wait on this event. - Simulate the Hardware and Network modules loading (use
Task.Delay). Once both are done, callSet()on_systemReady. - Create an
AutoResetEventnamed_taskAvailable. Create a worker thread that loops and waits for this event to process tasks. - The UI thread, after waking up, should trigger
_taskAvailablethree separate times to simulate three distinct tasks being queued.
There are no comments for now.