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
89: Parallel.For and Parallel.ForEach
When should I use Parallel.For versus Parallel.ForEach?
It's a simpler distinction than you might think. If you're dealing with a collection—like a List<T>, an array, or any IEnumerable—reach for Parallel.ForEach. It handles the iterator for you, and honestly, it's what I use 90% of the time because it's cleaner.
Parallel.For is for when you specifically need the index. If your logic depends on the current position (e.g., "if the index is even, do X; if odd, do Y") or if you're iterating through a range of numbers to perform a calculation, that's your tool.
// Use For when the index is the driver
Parallel.For(0, 1000, i => {
ComputeComplexMath(i);
});
// Use ForEach when the data is the driver
var files = Directory.GetFiles(@"C:\Logs");
Parallel.ForEach(files, file => {
ProcessLogFile(file);
});
Does this actually make my code faster every time?
Short answer: No. In fact, it can sometimes make your code slower. Parallelism isn't free; there is a significant overhead involved in partitioning the data, scheduling tasks on the ThreadPool, and managing thread synchronization.
I've seen developers try to use Parallel.ForEach on a list of ten strings to uppercase them, and it actually performed worse than a standard foreach loop. You want to use these when the work inside the loop is "heavy"—meaning it's CPU-intensive or takes a noticeable amount of time. If the loop body is just adding two numbers, stick to the sequential version. You're looking for a "grain size" that justifies the cost of spinning up those extra threads.
How do I stop my app from crashing when multiple threads touch the same variable?
This is where things get dangerous. If you have a shared variable—say, a total counter—and multiple threads try to increment it at the exact same millisecond, you'll run into a race condition. Your final count will be wrong, and you'll spend hours wondering why.
The easiest fix is the lock keyword, but that can create a bottleneck because threads have to wait in line. For simple numeric increments, I highly recommend the Interlocked class. It's way faster because it handles the synchronization at the CPU level.
int totalErrors = 0;
var logs = GetLogEntries();
Parallel.ForEach(logs, log => {
if (log.Contains("ERROR")) {
// DON'T DO: totalErrors++;
// DO THIS:
Interlocked.Increment(ref totalErrors);
}
});
Can I break out of a parallel loop early?
You can't use the break keyword here because the loop is running on multiple threads simultaneously; "breaking" one thread doesn't stop the others. Instead, you use the ParallelLoopState object, which is passed into the lambda expression.
Calling state.Stop() tells the system to try and stop all iterations as soon as possible. It's not an instantaneous "kill switch," but it's the correct way to signal that you've found what you were looking for and the rest of the work is unnecessary.
Parallel.ForEach(largeDataSet, (item, state) => {
if (item.IsCorrupted) {
Console.WriteLine("Found corruption! Stopping.");
state.Stop();
}
PerformHeavyCheck(item);
});
📋 Practical Task
Exercise: Parallel Image Metadata Processor
You are building a tool that scans a large directory of simulated "Image" objects to calculate the total size of all images and identify any image that exceeds a certain size limit.
Requirements:
- Create a class
ImageFilewith propertiesFileName(string) andFileSizeKB(int). - Generate a
List<ImageFile>containing 10,000 random images. - Use
Parallel.ForEachto iterate through the list. - Use
Interlocked.Addto maintain a running total of theFileSizeKBacross all threads. - Use a
ConcurrentBag<string>to store the names of images that are larger than 5,000 KB (sinceList<T>is not thread-safe for adding items). - Print the final total size and the count of oversized images to the console.
There are no comments for now.