Accessing value used in LINQ Select within a foreach

186 Views Asked by At

Given a list of IP addresses:

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

I am attempting to loop over each IP in the list, in a parallel fashion and then print a meaningful message to screen:

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}");
}

I am unable to access ip in that context. I would really like to understand how I could go about accessing each relative ip as I am sending them out?

I have explored the PingReply object but PingReply.Address as an example contains the host (sender) IP, so it cannot help with this requirement. I really wish the PingReply object contained the Ip that was pinged!


UPDATE

As per example provided by @haim770 and @MindSwipe I ended up using:

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}");
}

UPDATE 2

As per comment from @pinkfloydx33 regarding use of ValueTuple I have done as per the following example:

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}");
}
2

There are 2 best solutions below

4
MindSwipe On BEST ANSWER

You're currently only selecting the pingReply, not the ip and the pingReply, to do that you'll need to select a new anonymous type and iterate over that. Like so:

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}");
}

Edit: Just noticed now that haim770 posted basically this in their comment

Edit 2: Thanks pinkfloydx33 for pointing out I could use tuple deconsturcting

5
Theodor Zoulias On

Calling the synchronous Ping.Send in parallel with a degree of parallelism = 64 is quite inefficient, because as many as 64 threads are blocked during the parallel execution (provided that the ThreadPool has enough threads available to satisfy the demand, which is doubtful). A more efficient way to do the pinging is to use the asynchronous Ping.SendPingAsync method. To invoke this method in a way that no more than 64 asynchronous operations will be simultaneously in-flight, you will need a Parallel.ForEach equivalent that works with asynchronous delegates. Currently there is no such thing available built-in (it will probably be available in .NET 6), but you can find lots of custom implementations if you search for ForEachAsync. There is one here for example. Then you will be able to do this:

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();

You could also await the completion of the operation, instead of using the blocking Wait, provided that you are calling it from an async method.