C# 在循环中执行 http 请求,我想要结果但不想在处理下一个循环之前等待

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

我正在编写一个方法 GetComputers(),它采用 IP 地址字符串列表。我想从这个字符串列表中构建一个具有 IPAddress 和 Name 属性的 Computer 对象列表。要获取名称,我需要向该地址发送 http 请求。因此,我想循环遍历 IP 地址列表,并为每个 IP 地址发送一个使用该地址获取名称的 http 请求。但是,我希望 http 请求能够并行完成。我不想等待每个 http 请求返回才开始处理下一个 ip 地址。我想知道我的方法是否正确。

以下是代码摘录:

电脑班:

public class Computer 
{
   public string IPAddress {get; set;}
   public string Name {get;set;}
}

具有 GetComputers() 方法的类(我已简化它并删除了任何错误处理)

public class MyClass 
{
    public static HttpClient client = new HttpClient();

    public List<Computer> GetComputers(List<string> ipAddresses)
    {
        List<Computer> computers = new List<Computer>();
        List<Task> tasks = new List<Tasks>();
        
        foreach (string ipAddress in ipAddresses)
        { 
           Computer computer = new Computer();
           computer.IPAddress = ipAddress;
           Task task = Task.Run(() => {
            computer.Name = GetName(ipAddress);
           });
           
           computers.Add(computer);
           tasks.Add(task);
        }
        
        Task.WaitAll(tasks.ToArray());
        return computers;
    }

    private async Task<string> GetName(string ipAddress)
    {
        HttpResponseMessage response = await client.GetAsync("http://" + ipAddress + "/api/name");
        string name = await response.Content.ReadAsStringAsync();
        
        return name;
    }
}

到目前为止,这对我有用,但我想知道这是否是 Task.Run() 的正确使用,或者是否有更好的方法

c# .net dotnet-httpclient
1个回答
0
投票

如所呈现的,我很惊讶您的代码可以编译,因为

computer.Name = GetName(ipAddress);
正在将
Task<string>
分配给
string
。即使解决了这个问题,您的方法也存在一些问题,我建议您阅读惯用的异步/等待编程(您通常不应该使用
Task.Run
)。

我会推荐这样的东西:

public List<Computer> GetComputers(List<string> ipAddresses) {
  return (
    await Task.WhenAll(
      ipAddresses
        .Select(async ipAddress => {
          var name = await GetName(ipAddress);
          return new Computer {
            IPAddress = ipAddress,
            Name = name
          };
        })
    )
  ).ToList();
}

里面有一些有用的技巧。关键是

await Task.WhenAll(things.Select(async thing => await ...))
Task.WhenAll
获取
Task
的集合,并将其转换为包含结果的
Task
,因此您只需等待一次。

实际上在幕后发生的事情有点复杂,但本质上

Select
循环遍历您的集合,触发
Task
,但不等待它们完成,然后
await Task.WhenAll(...)
接收它们并等待它们全部,给出最后给你结果。

Google 的其他内容:

  • IEnumerable
    和 Linq(
    Select
    来自哪里)
  • 对象初始化器(
    new Computer
    语法)
  • 函数式编程,如果您想避免从多线程代码向列表添加内容时出现的各种错误。 Tldr:永远不要改变任何东西,而且不会出现太严重的错误。
© www.soinside.com 2019 - 2024. All rights reserved.