在 .net Core、C# 中并行调用 API 的最佳方法是什么?

What is the best way to cal API calls in parallel in .net Core, C#?

我想并行调用我的 API x 次,以便可以快速完成处理。 我有以下三种方法,我必须并行调用 APIs。我正在尝试了解执行此操作的最佳方式。

基本代码

var client = new System.Net.Http.HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");

client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com");
var list = new List<int>();

var listResults = new List<string>();
for (int i = 1; i < 5; i++)
{
    list.Add(i);
}

第一种方法使用 Parallel.ForEach

Parallel.ForEach(list,new ParallelOptions() { MaxDegreeOfParallelism = 3 }, index =>
{
    var response = client.GetAsync("posts/" + index).Result;

    var contents =  response.Content.ReadAsStringAsync().Result;
    listResults.Add(contents);
    Console.WriteLine(contents);
});

Console.WriteLine("After all parallel tasks are done with Parallel for each");

带任务的第二种方法。我不确定这是否并行。如果可以,请告诉我

var loadPosts = new List<Task<string>>();
foreach(var post in list)
{
    var response = await client.GetAsync("posts/" + post);

    var contents = response.Content.ReadAsStringAsync();
    loadPosts.Add(contents);
    Console.WriteLine(contents.Result);
}

await Task.WhenAll(loadPosts);

Console.WriteLine("After all parallel tasks are done with Task When All");

使用操作块的第三种方法 - 这是我认为我应该一直做的事情,但我想听听社区的意见

var responses = new List<string>();

var block = new ActionBlock<int>(
    async x => {
        var response = await client.GetAsync("posts/" + x);
        var contents = await response.Content.ReadAsStringAsync();
        Console.WriteLine(contents);
        responses.Add(contents);                
    },
    new ExecutionDataflowBlockOptions
    {
        MaxDegreeOfParallelism = 6, // Parallelize on all cores
    });

for (int i = 1; i < 5; i++)
{
    block.Post(i);
}

block.Complete();
await block.Completion;

Console.WriteLine("After all parallel tasks are done with Action block");

接近 2 号。这是一个经验法则:I/O 绑定操作=> 使用 Tasks/WhenAll(异步),计算绑定操作 => 使用并行。 Http 请求是网络 I/O。

            foreach (var post in list)
            {
                async Task<string> func()
                {
                    var response = await client.GetAsync("posts/" + post);
                    return await response.Content.ReadAsStringAsync();
                }

                tasks.Add(func());
            }

            await Task.WhenAll(tasks);

            var postResponses = new List<string>();

            foreach (var t in tasks) {
                var postResponse = await t; //t.Result would be okay too.
                postResponses.Add(postResponse);
                Console.WriteLine(postResponse);
            }

我会使用以下方法,它无法控制并发性(它将并行分派所有 HTTP 请求,与您的第三种方法不同)但它要简单得多 - 它只有一个 await

var client = new HttpClient();
var list = new[] { 1, 2, 3, 4, 5 };
var postTasks = list.Select(p => client.GetStringAsync("posts/" + p));
var posts = await Task.WhenAll(postTasks);
foreach (var postContent in posts)
{
    Console.WriteLine(postContent);
}

我制作了一个小的控制台应用程序来测试所有方法 ping API "https://jsonplaceholder.typicode.com/todos/{i}" 200 次。 @MikeLimaSierra 方法 1 或 3 是最快的!

Method DegreeOfParallelism Time
Not Parallel n/a 8.4 sec
@LearnAspNet (OP) Method 1 2 5.494 sec
@LearnAspNet (OP) Method 1 30 1.235 sec
@LearnAspNet (OP) Method 3 2 4.750 sec
@LearnAspNet (OP) Method 3 30 1.795 sec
@jamespconnor Method n/a 21.5 sec
@YuliBonner Method n/a 21.4 sec