任务并行库 - 哪种方法最好,为什么?

问题描述 投票:0回答:1

我正在尝试了解 .NET 中的线程和任务并行库。因此,我正在尝试使用两种方法同时运行任务,如下所示 -

一些背景 - 我从 https://jsonplaceholder.typicode.com/photos 端点获得了 5000 张照片的列表,我想下载这些照片(被丢弃,但本质上模仿下载)。我正在尝试使用不同的方法来做到这一点,并找出每种方法所花费的时间以及原因。

  1. 第一种方法是连续的,下载一张又一张照片,所需时间最长(大约 24 分钟)。这是可以理解的,因为我正在等待完成上一张照片的下载,直到下一张照片开始。所以这里没有抱怨。

  2. 第二种方法使用

    List<Task>
    并将每个照片下载任务添加到列表中,最后等待所有任务完成。这大约需要 1 分 7 秒。由于它并行下载多张照片,因此与第一种即顺序方法相比,预计时间会更短,情况也是如此。 .

  3. 第三种方法使用

    Parallel.ForEachAsync()
    。令我惊讶的是,下载所有照片花了 5 分 19 秒。我期望它的表现与第二种方法类似,但事实并非如此。

using System.Diagnostics;
using System.Text.Json;

var httpClient = new HttpClient();

var photosResponse = await httpClient.GetAsync("https://jsonplaceholder.typicode.com/photos");
var content = await photosResponse.Content.ReadAsStringAsync();
var photos = JsonSerializer.Deserialize<List<Photo>>(content, new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true
})!;

var stopwatch = new Stopwatch();
stopwatch.Start();

// // 1.Sequential - Time taken: 23m 58s
// foreach (var photo in photos)
// {
//     Console.WriteLine($"Downloading {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
//     var imageResponse = await httpClient.GetAsync(photo.Url);
//     _ = await imageResponse.Content.ReadAsByteArrayAsync();
//     Console.WriteLine($"Downloaded {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
// }

// 2.Tasks - Time taken: 1m 7s
var tasks = new List<Task>();
foreach (var photo in photos!)
{
    tasks.Add(DownloadPhotoTask(photo, httpClient));
}
await Task.WhenAll(tasks);

// // 3.Parallel.ForEach - Time taken: 5m 19s
// await Parallel.ForEachAsync(photos, (photo, _) => DownloadPhotoValueTask(photo, httpClient));

stopwatch.Stop();

Console.WriteLine($"Time elapsed: {stopwatch.Elapsed}");
return;


async Task DownloadPhotoTask(Photo photo, HttpClient httpClientInternal)
{
    Console.WriteLine($"Downloading {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
    var imageResponse = await httpClientInternal.GetAsync(photo.Url);
    _ = await imageResponse.Content.ReadAsByteArrayAsync();
    Console.WriteLine($"Downloaded {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
}

async ValueTask DownloadPhotoValueTask(Photo photo, HttpClient httpClientInternal)
{
    Console.WriteLine($"Downloading {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
    var imageResponse = await httpClientInternal.GetAsync(photo.Url);
    _ = await imageResponse.Content.ReadAsByteArrayAsync();
    Console.WriteLine($"Downloaded {photo.Id} on Thread {Environment.CurrentManagedThreadId}");
}
public record Photo(int Id, string Title, string Url);

有人可以帮助我理解第二种方法和第三种方法所花费的时间之间的显着差异吗?哪个更好?如果持续时间是唯一的参数,那么显然从我的测试来看,第二个是最好的方法。如果是的话,为什么我们需要

Parallel.ForEachAsync()

此外,如果您可以详细说明第二种和第三种方法的内部工作原理,那将会很有帮助。

c# multithreading task-parallel-library threadpool
1个回答
0
投票

Parallel.ForEachAsync
限制并行度的选项,该方法默认设置为
Environment.ProcessorCount
源代码)。如果您想以无限并行度下载图像,冒着被视为DOS攻击者并被远程服务器阻止的风险,您可以将此选项设置为
Int32.MaxValue
:

ParallelOptions parallelOptions = new()
{
    MaxDegreeOfParallelism = Int32.MaxValue
};

await Parallel.ForEachAsync(photos, parallelOptions, async (pair, ct) =>
{
    await DownloadPhotoValueTask(photo, httpClient);
});

顺便说一句,建议您将

cancellationToken
参数传递给 API
HttpClient.GetAsync
HttpContent.ReadAsByteArrayAsync
,以便在出现错误时更快地完成并行循环。

© www.soinside.com 2019 - 2024. All rights reserved.