Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> C#

Khác biệt giữa foreach và Parallel.ForEach trong C#

So sánh nhanh

Đặc điểmforeachParallel.ForEach
ThreadingSingle-thread (sequential)Multi-thread (parallel)
Thứ tự thực thiĐảm bảo thứ tựKhông đảm bảo thứ tự
NamespaceKhông cần (keyword)System.Threading.Tasks
OverheadThấpCao (thread pooling, partitioning)
Phù hợpCPU-bound nhẹ, I/O sequential, cần thứ tựCPU-bound nặng, independent items
Exception handlingTry-catch bình thườngAggregateException (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 SemaphoreSlim hoặc Parallel.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.