【问题标题】:Accessing value used in LINQ Select within a foreach在 foreach 中访问 LINQ Select 中使用的值
【发布时间】:2021-05-19 01:51:00
【问题描述】:

给定一个 IP 地址列表:

List<string> ipList = new List<string>(); //example: 192.168.0.1, 192.168.0.2, 192.168.0.3 etc.

我正在尝试以并行方式遍历列表中的每个 IP,然后在屏幕上打印一条有意义的消息:

foreach (PingReply pingReply in ipList.AsParallel().WithDegreeOfParallelism(64).Select(ip => new Ping().Send(ip)))
{
    Console.WriteLine($"Ping status: {pingReply.Status} for the target IP address: {ip}");
}

在这种情况下,我无法访问 ip。我真的很想了解在发送它们时如何访问每个 relative ip

我已经探索了PingReply 对象,但以PingReply.Address 为例,它包含主机(发件人)IP,因此无法满足此要求。我真的希望 PingReply 对象包含被 ping 的 Ip!


更新

根据@haim770 和@MindSwipe 提供的示例,我最终使用了:

foreach (var pingResponseData in ipList.AsParallel().WithDegreeOfParallelism(64).Select(ip => new { ip, pingReply = new Ping().Send(ip) }))
{
    Console.WriteLine($"Ping status: {pingResponseData.pingReply.Status} for the target IP address: {pingResponseData.ip}");
}

更新 2

根据@pinkfloydx33 关于使用ValueTuple 的评论,我已按照以下示例完成:

foreach (var (ip, reply) in ipList.AsParallel().WithDegreeOfParallelism(ipList.Count).Select(ip => (ip, new Ping().Send(ip, 150))))
{
    Console.WriteLine($"Ping status: {reply.Status} for the target IP address: {ip}");
}

【问题讨论】:

  • 只需使用.Select(ip =&gt; new { ip, pingResult = new Ping().Send(ip) })
  • 改用Parallel.ForEach(ipList, ip =&gt; { /*code to do ping and write result*/ });。它会更容易阅读。
  • 谢谢@haim770!您的回应是有道理且有效的。

标签: c# linq plinq


【解决方案1】:

您目前只选择pingReply,而不是ippingReply,为此您需要选择一个新的anonymous type 并对其进行迭代。像这样:

foreach (var (pingReply, ip) in ipList.AsParallel().WithDegreeOfParallelism(64).Select(ip => (ip, Ping().Send(ip))))
{
    // Here 'i' is an object with the properties 'ip' and 'pingReply'
    Console.WriteLine($"Ping status: {i.pingReply.Status} for the target IP address: {i.ip}");
}

编辑:现在刚刚注意到haim770 基本上在their comment 中发布了这个

编辑 2:感谢pinkfloydx33 指出我可以使用tuple deconsturcting

【讨论】:

  • 感谢@MindSwipe 的示例!说得通。 (附言只是仔细检查你的例子有一个错字没有关闭}
  • 啊,谢谢@BernardV,因为我现在坐在学校里,所以没有注意到缺少的右括号。我编辑了答案。如果它回答了您的问题,请务必接受我的回答,以便未来的读者知道它成功了(cmets 倾向于不留下来)
  • 如果你选择ValueTuple,你可以在foreach中解构它...可能更清楚foreach (var (ip, reply) in ipList.XXX.Select(ip =&gt; (ip, new Ping().Send(ip)))) { Console.WriteLine($"status {reply.Status} - ip {ip}“); }
  • 啊,谢谢@pinkfloydx33,我在技术上知道,但我的大脑一片空白。我将在使用计算机后编辑我的问题
【解决方案2】:

以并行度 = 64 并行调用同步的Ping.Send 效率相当低,因为在并行执行期间阻塞了多达 64 个线程(前提是ThreadPool 有足够的可用线程来满足需求,这是值得怀疑的)。执行 ping 的更有效方法是使用异步 Ping.SendPingAsync 方法。要以不超过 64 个异步操作同时进行的方式调用此方法,您将需要与异步委托一起使用的 Parallel.ForEach 等效项。目前没有这样的内置功能(在 .NET 6 中可能是 be available),但是如果您搜索 ForEachAsync,您可以找到很多自定义实现。例如有一个here。然后你就可以这样做了:

var ipList = new List<string>() {"192.168.0.1", "192.168.0.2", "192.168.0.3"}; // etc

ipList.ForEachAsync(async ip =>
{
    var ping = new Ping();
    var reply = await ping.SendPingAsync(ip);
    Console.WriteLine($"IP '{ip}' ping reply status: {reply.Status}");
}, dop: 64).Wait();

你也可以await完成操作,而不是使用阻塞Wait,前提是你从async方法调用它。

【讨论】:

  • 谢谢@Theodor Zoulias 我会探讨你的推荐;在确定 AsParallel().WithDegreeOfParallelism 方法之前,我确实通过以下示例探索了异步 ping 方法:stackoverflow.com/a/22078703/3324415 但由于某种原因,完成整个任务并没有感觉更快(我正在 ping 大约 254 个 ip)。
  • 如果你有兴趣看看这个小控制台应用程序,我在这里github.com/BVisagie/Live-Host-Sweeper;感谢您对我的问题的反馈谢谢!
  • @BernardV 我无法解释为什么它没有更快。可能还有其他一些限制因素,例如每个域的某些内部最大同时 ping 数。我不知道,只是猜测。
  • 嗨@Theodor Zoulias 我已经实施了你的建议,它似乎非常快,我想知道由于结果排序的性质是否可以做任何事情,我假设不是因为无法控制任务完成?我这里有一个在线示例:dotnetfiddle.net/jZrrjy
  • @BernardV 我猜你想获得PingReply 结果与源列表中的IP 相同的顺序。在这种情况下,您可能需要提供与.AsParallel().AsOrdered() PLINQ 配置类似的功能的ForEachAsync 版本。可惜内置的并行库还没有支持异步,我们必须使用自定义解决方案,或者自己编写。你可以找到这样的实现here
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多