Ответ
Для параллельного выполнения независимых задач в C# есть несколько основных подходов. Выбор зависит от необходимости ожидания результатов и обработки исключений.
1. Task.WhenAll — ожидание завершения всех задач
Используется, когда нужно запустить задачи параллельно и дождаться результатов всех из них.
public async Task ProcessDataInParallelAsync()
{
var task1 = DownloadFileAsync("url1");
var task2 = ProcessImageAsync("image.jpg");
var task3 = QueryDatabaseAsync();
// Все задачи выполняются параллельно. Ожидаем завершения всех.
await Task.WhenAll(task1, task2, task3);
// Получаем результаты (если задачи возвращают значение).
var file = await task1; // await здесь мгновенный, т.к. задача уже завершена.
var result = task2.Result; // Альтернатива для уже завершенной задачи.
}
2. Task.WhenAny — ожидание первой завершенной задачи
Полезно для реализации таймаутов или выбора самого быстрого источника данных.
public async Task<string> GetFastestResponseAsync()
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var tasks = new List<Task<string>>
{
CallServiceAAsync(cts.Token),
CallServiceBAsync(cts.Token)
};
// Ожидаем, пока любая из задач завершится успешно.
Task<string> completedTask = await Task.WhenAny(tasks);
// Отменяем оставшиеся задачи.
cts.Cancel();
// Возвращаем результат первой завершенной задачи.
return await completedTask;
}
3. Parallel.For / Parallel.ForEach (для CPU-bound операций)
Используется для параллельной обработки данных в коллекциях, когда операции требуют интенсивных вычислений.
public void TransformDataInParallel(List<DataItem> items)
{
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount };
Parallel.ForEach(items, parallelOptions, item =>
{
// Вычислительно сложная операция над каждым элементом.
item.Value = HeavyComputation(item.RawData);
});
}
4. Параллельный запуск с ограничением степени параллелизма (SemaphoreSlim)
Когда нужно выполнить много I/O-bound задач (например, HTTP-запросов), но с ограничением на их одновременное количество.
public async Task ProcessUrlsWithThrottlingAsync(List<string> urls, int maxConcurrent)
{
var semaphore = new SemaphoreSlim(maxConcurrent);
var tasks = urls.Select(async url =>
{
await semaphore.WaitAsync();
try
{
return await DownloadStringAsync(url);
}
finally
{
semaphore.Release();
}
});
await Task.WhenAll(tasks);
}
Ключевой момент: Task.WhenAll и Task.WhenAny не запускают задачи — они лишь создают задачи-ожидания. Задачи начинают выполняться в момент их создания (если только они не созданы с опцией TaskCreationOptions.RunContinuationsAsynchronously). Для CPU-bound операций предпочтительнее Parallel или Parallel LINQ (PLINQ).