So sánh nhanh
| Đặc điểm | foreach | Parallel.ForEach |
|---|---|---|
| Threading | Single-thread (sequential) | Multi-thread (parallel) |
| Thứ tự thực thi | Đảm bảo thứ tự | Không đảm bảo thứ tự |
| Namespace | Không cần (keyword) | System.Threading.Tasks |
| Overhead | Thấp | Cao (thread pooling, partitioning) |
| Phù hợp | CPU-bound nhẹ, I/O sequential, cần thứ tự | CPU-bound nặng, independent items |
| Exception handling | Try-catch bình thường | AggregateException (chứa nhiều exception) |
Ví dụ so sánh hiệu năng
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
class Demo
{
static void Main()
{
var animals = new List<string>
{
"cat", "rat", "deer", "elephant", "lion", "tiger", "dog", "pig",
"buffalo", "rabbit", "horse", "cheetah", "cow", "goat", "sheep",
"donkey", "zebra", "wolf", "fox", "leopard", "monkey", "kangaroo",
"giraffe", "hippo"
};
// 1. foreach tuần tự
var sw1 = Stopwatch.StartNew();
foreach (var animal in animals)
{
SimulateWork(animal);
}
sw1.Stop();
Console.WriteLine($"foreach: {sw1.ElapsedMilliseconds} ms");
// 2. Parallel.ForEach
var sw2 = Stopwatch.StartNew();
Parallel.ForEach(animals, animal =>
{
SimulateWork(animal);
});
sw2.Stop();
Console.WriteLine($"Parallel.ForEach: {sw2.ElapsedMilliseconds} ms");
// 3. Parallel.ForEach với options
var sw3 = Stopwatch.StartNew();
Parallel.ForEach(animals, new ParallelOptions { MaxDegreeOfParallelism = 4 }, animal =>
{
SimulateWork(animal);
});
sw3.Stop();
Console.WriteLine($"Parallel.ForEach (Max 4 threads): {sw3.ElapsedMilliseconds} ms");
Console.ReadKey();
}
static void SimulateWork(string item)
{
// Giả lập CPU-bound work
Thread.Sleep(50);
Console.WriteLine($"Processed: {item} (Thread: {Thread.CurrentThread.ManagedThreadId})");
}
}
Kết quả mẫu
foreach: 1200 ms Parallel.ForEach: 350 ms Parallel.ForEach (Max 4 threads): 600 ms
Các tùy chọn ParallelOptions
var options = new ParallelOptions
{
MaxDegreeOfParallelism = 4, // Giới hạn số thread đồng thời (-1 = không giới hạn)
CancellationToken = cts.Token, // Hỗ trợ hủy bỏ
TaskScheduler = customScheduler // Custom scheduler (hiếm dùng)
};
Parallel.ForEach(items, options, item => { ... });
Xử lý Exception
try
{
Parallel.ForEach(items, item =>
{
if (item == "bad") throw new InvalidOperationException("Lỗi!");
Process(item);
});
}
catch (AggregateException ae)
{
foreach (var ex in ae.InnerExceptions)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
Parallel.ForEachAsync (.NET 6+)
Cho async/await trong body:
await Parallel.ForEachAsync(urls, async (url, ct) =>
{
await httpClient.GetAsync(url, ct);
Console.WriteLine($"Downloaded: {url}");
});
Khi nào KHÔNG nên dùng Parallel.ForEach
- Collection nhỏ (< 100 items) - overhead lớn hơn gain.
- Cần giữ thứ tự xử lý (order matters).
- Item phụ thuộc lẫn nhau (shared state, race condition).
- I/O-bound đơn giản → dùng
SemaphoreSlimhoặcParallel.ForEachAsync. - Gọi code không thread-safe (legacy COM, non-thread-safe libs).
PLINQ - Alternative
using System.Linq;
// Parallel LINQ - syntax quen thuộc
var results = items
.AsParallel()
.WithDegreeOfParallelism(4)
.Where(x => x.IsValid)
.Select(x => Process(x))
.ToList();
Tóm tắt quyết định
Quy tắc: Mặc định dùng
foreach. Chỉ dùng Parallel.ForEach khi: (1) Workload CPU-bound đáng kể, (2) Items độc lập, (3) Collection đủ lớn, (4) Đã benchmark chứng minh nhanh hơn.