← Назад к вопросам

Как tasks выполнить параллельно?

2.0 Middle🔥 203 комментариев
#Асинхронность и многопоточность#Основы C# и .NET

Комментарии (3)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Параллельное выполнение задач (Tasks) в C#

В C# существует несколько подходов для параллельного выполнения задач (Tasks), каждый из которых имеет свои особенности и сценарии применения. Вот основные методы:

1. Parallel.For и Parallel.ForEach

Эти методы из пространства имен System.Threading.Tasks.Parallel используются для параллельной обработки коллекций.

using System.Threading.Tasks;

// Parallel.For - для диапазонов
var data = new int[1000];
Parallel.For(0, data.Length, i => 
{
    data[i] = i * i;  // Параллельное вычисление квадратов
});

// Parallel.ForEach - для коллекций
var urls = new List<string> { "url1", "url2", "url3" };
Parallel.ForEach(urls, url => 
{
    DownloadData(url);  // Параллельная загрузка данных
});

Параллельные опции:

var options = new ParallelOptions 
{
    MaxDegreeOfParallelism = 4  // Ограничение количества потоков
};

Parallel.ForEach(items, options, item => 
{
    ProcessItem(item);
});

2. Task.WhenAll

Оптимальный способ для запуска нескольких независимых задач и ожидания их завершения.

public async Task ProcessMultipleOperationsAsync()
{
    // Создание задач
    var task1 = ProcessDataAsync("data1");
    var task2 = ProcessDataAsync("data2");
    var task3 = ProcessDataAsync("data3");
    
    // Параллельное выполнение с ожиданием всех задач
    await Task.WhenAll(task1, task2, task3);
    
    // Обработка результатов
    var results = new[] { await task1, await task2, await task3 };
}

Практический пример с коллекцией:

public async Task<List<string>> DownloadAllAsync(IEnumerable<string> urls)
{
    var downloadTasks = urls.Select(url => DownloadStringAsync(url));
    var results = await Task.WhenAll(downloadTasks);
    return results.ToList();
}

3. Task.Run для CPU-bound операций

Для вычислительных задач, которые блокируют поток:

// Запуск тяжелых вычислений в пуле потоков
var tasks = new Task<int>[]
{
    Task.Run(() => CalculatePrimeNumbers(1, 100000)),
    Task.Run(() => CalculatePrimeNumbers(100001, 200000)),
    Task.Run(() => CalculatePrimeNumbers(200001, 300000))
};

var results = await Task.WhenAll(tasks);

4. Параллельные операции с PLINQ

Для параллельной обработки данных с использованием LINQ:

using System.Linq;

var numbers = Enumerable.Range(1, 1000000);

// Параллельная обработка с сохранением порядка
var processedNumbers = numbers.AsParallel()
                             .AsOrdered()
                             .Where(n => IsPrime(n))
                             .Select(n => n * 2)
                             .ToList();

// Агрегация с параллельным выполнением
var sum = numbers.AsParallel()
                 .WithDegreeOfParallelism(Environment.ProcessorCount)
                 .Sum();

5. Шаблон Producer-Consumer с BlockingCollection

Для сложных сценариев параллельной обработки:

using System.Collections.Concurrent;

public async Task ProcessInParallelAsync()
{
    var queue = new BlockingCollection<string>();
    var producer = Task.Run(() => ProduceItems(queue));
    
    var consumers = Enumerable.Range(0, 4)
        .Select(_ => Task.Run(() => ConsumeItems(queue)))
        .ToArray();
    
    await Task.WhenAll(consumers);
    await producer;
}

Ключевые рекомендации

Ограничение параллелизма

Всегда ограничивайте степень параллелизма для предотвращения исчерпания ресурсов:

// С использованием SemaphoreSlim
private static readonly SemaphoreSlim _semaphore = new(10, 10);

public async Task ProcessWithThrottlingAsync()
{
    var tasks = urls.Select(async url =>
    {
        await _semaphore.WaitAsync();
        try
        {
            return await DownloadAsync(url);
        }
        finally
        {
            _semaphore.Release();
        }
    });
    
    return await Task.WhenAll(tasks);
}

Обработка исключений

При параллельном выполнении правильно обрабатывайте исключения:

try
{
    await Task.WhenAll(tasks);
}
catch (AggregateException ae)
{
    foreach (var e in ae.InnerExceptions)
    {
        // Логирование или обработка исключений
    }
}

Выбор подхода

  • Для обработки данных - используйте Parallel.For/ForEach или PLINQ
  • Для I/O операций - используйте Task.WhenAll с async/await
  • Для CPU-intensive задач - применяйте Task.Run в пуле потоков
  • Для сложных конвейеров - рассмотрите шаблоны Producer-Consumer

Важно помнить, что параллельность не всегда приводит к улучшению производительности. Измеряйте производительность, учитывайте накладные расходы на создание потоков и синхронизацию, и выбирайте подход в зависимости от конкретной задачи и характеристик оборудования.

Как tasks выполнить параллельно? | PrepBro